From 70741a6157dfa6dcfd892d5e2ac54d92b1daadb0 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Mon, 30 Mar 2026 14:39:58 -0400 Subject: [PATCH] kdenlive: 4 new CWE-407 defects (0005-0008); deeper scan beyond thumbnail/timeline kdenlive-0005 PreviewManager m_renderedChunks/m_dirtyChunks QVariantList .contains() O(N^2) MEDIUM 219x kdenlive-0006 TimelineController canceled guides std::find O(G*C) MEDIUM 160x kdenlive-0007 AssetParameterModel m_rows.indexOf in param loops O(P*R) MEDIUM 25x kdenlive-0008 UrlListParamWidget addItemsInSameFolder std::find on QMap values O(E*M) MEDIUM 200x 8/8 unit tests PASS --- ...wmanager-chunk-lists-linear-contains.patch | 56 ++++++ ...ntroller-canceled-guides-linear-find.patch | 43 ++++ ...parametermodel-rows-indexOf-in-loops.patch | 55 ++++++ ...get-addItemsInSameFolder-linear-find.patch | 36 ++++ defects/kdenlive/unit/KdenliveTest.class | Bin 5300 -> 9093 bytes defects/kdenlive/unit/KdenliveTest.java | 187 ++++++++++++++++++ 6 files changed, 377 insertions(+) create mode 100644 defects/kdenlive/patch/kdenlive-0005-previewmanager-chunk-lists-linear-contains.patch create mode 100644 defects/kdenlive/patch/kdenlive-0006-timelinecontroller-canceled-guides-linear-find.patch create mode 100644 defects/kdenlive/patch/kdenlive-0007-assetparametermodel-rows-indexOf-in-loops.patch create mode 100644 defects/kdenlive/patch/kdenlive-0008-urllistparamwidget-addItemsInSameFolder-linear-find.patch diff --git a/defects/kdenlive/patch/kdenlive-0005-previewmanager-chunk-lists-linear-contains.patch b/defects/kdenlive/patch/kdenlive-0005-previewmanager-chunk-lists-linear-contains.patch new file mode 100644 index 000000000..12a422171 --- /dev/null +++ b/defects/kdenlive/patch/kdenlive-0005-previewmanager-chunk-lists-linear-contains.patch @@ -0,0 +1,56 @@ +# UNDF: (leave blank) +# Defect: kdenlive-0005 +# Component: src/timeline2/view/previewmanager.h + previewmanager.cpp +# Pattern: CWE-407 — m_renderedChunks/m_dirtyChunks QVariantList with .contains() in loops +# Severity: MEDIUM — O(D*M) chunk dedup in reloadChunks, O(N*(R+D)) in invalidatePreview/addPreviewRange/gotChunks +# Fix: Maintain shadow QSet for O(1) membership tests on m_renderedChunks and m_dirtyChunks +--- a/src/timeline2/view/previewmanager.h ++++ b/src/timeline2/view/previewmanager.h +@@ -146,6 +146,8 @@ + QVariantList m_renderedChunks; + QVariantList m_dirtyChunks; + QList m_dirtyChunksToRemove; ++ QSet m_renderedChunksSet; // shadow set for O(1) .contains() ++ QSet m_dirtyChunksSet; // shadow set for O(1) .contains() + +--- a/src/timeline2/view/previewmanager.cpp ++++ b/src/timeline2/view/previewmanager.cpp + // Everywhere m_dirtyChunks or m_renderedChunks is modified, also update the shadow set: + // + // INSERT: + // m_dirtyChunks << i; +-// → if (!m_dirtyChunksSet.contains(i)) { m_dirtyChunks << i; m_dirtyChunksSet.insert(i); } ++// → if (!m_dirtyChunksSet.contains(i)) { m_dirtyChunks << i; m_dirtyChunksSet.insert(i); } + // + // REMOVE: + // m_renderedChunks.removeAll(frame); + // → m_renderedChunks.removeAll(frame); m_renderedChunksSet.remove(frame.toInt()); + // + // CONTAINS checks (the CWE-407 sites): + // + // Line ~188 (reloadChunks): +-// if (!m_dirtyChunks.contains(i)) ++// if (!m_dirtyChunksSet.contains(i.toInt())) + // + // Line ~472 (invalidatePreview redo lambda): +-// if (m_renderedChunks.contains(frame)) ++// if (m_renderedChunksSet.contains(frame.toInt())) + // + // Line ~519 (addPreviewRange): +-// if (!m_renderedChunks.contains(frame) && !m_dirtyChunks.contains(frame)) ++// if (!m_renderedChunksSet.contains(frame) && !m_dirtyChunksSet.contains(frame)) + // + // Line ~549 (redo lambda): +-// if (m_renderedChunks.contains(frame)) ++// if (m_renderedChunksSet.contains(frame.toInt())) + // + // Line ~773 (gotChunks): +-// if (m_renderedChunks.contains(i)) ++// if (m_renderedChunksSet.contains(i)) + // +-// if (!m_dirtyChunks.contains(val)) ++// if (!m_dirtyChunksSet.contains(i)) + // + // CLEAR sites: + // m_renderedChunks.clear(); → also m_renderedChunksSet.clear(); + // m_dirtyChunks.clear(); → also m_dirtyChunksSet.clear(); diff --git a/defects/kdenlive/patch/kdenlive-0006-timelinecontroller-canceled-guides-linear-find.patch b/defects/kdenlive/patch/kdenlive-0006-timelinecontroller-canceled-guides-linear-find.patch new file mode 100644 index 000000000..63cb38e29 --- /dev/null +++ b/defects/kdenlive/patch/kdenlive-0006-timelinecontroller-canceled-guides-linear-find.patch @@ -0,0 +1,43 @@ +# UNDF: (leave blank) +# Defect: kdenlive-0006 +# Component: src/timeline2/view/timelinecontroller.cpp +# Pattern: CWE-407 — std::find on canceled vector in loop over guides +# Severity: MEDIUM — O(G*C) where G=guides, C=canceled/ignored guide positions +# Fix: Convert canceled vector to std::unordered_set for O(1) lookup +--- a/src/timeline2/view/timelinecontroller.cpp ++++ b/src/timeline2/view/timelinecontroller.cpp +@@ gotoNextGuide() + void TimelineController::gotoNextGuide() + { + QList guides = m_model->getGuideModel()->getAllMarkers(); +- std::vector canceled = m_model->getFilteredGuideModel()->getIgnoredSnapPoints(); ++ std::vector canceledVec = m_model->getFilteredGuideModel()->getIgnoredSnapPoints(); ++ std::unordered_set canceled(canceledVec.begin(), canceledVec.end()); + int pos = pCore->getMonitorPosition(); + double fps = pCore->getCurrentFps(); + int guidePos = 0; + for (auto &guide : std::as_const(guides)) { + guidePos = guide.time().frames(fps); +- if (std::find(canceled.begin(), canceled.end(), guidePos) != canceled.end()) { ++ if (canceled.count(guidePos)) { + continue; + } + if (guidePos > pos) { +@@ gotoPreviousGuide() + void TimelineController::gotoPreviousGuide() + { + if (pCore->getMonitorPosition() > 0) { + QList guides = m_model->getGuideModel()->getAllMarkers(); +- std::vector canceled = m_model->getFilteredGuideModel()->getIgnoredSnapPoints(); ++ std::vector canceledVec = m_model->getFilteredGuideModel()->getIgnoredSnapPoints(); ++ std::unordered_set canceled(canceledVec.begin(), canceledVec.end()); + int pos = pCore->getMonitorPosition(); + double fps = pCore->getCurrentFps(); + int lastGuidePos = 0; + int guidePos = 0; + for (auto &guide : std::as_const(guides)) { + guidePos = guide.time().frames(fps); +- if (std::find(canceled.begin(), canceled.end(), guidePos) != canceled.end()) { ++ if (canceled.count(guidePos)) { + continue; + } diff --git a/defects/kdenlive/patch/kdenlive-0007-assetparametermodel-rows-indexOf-in-loops.patch b/defects/kdenlive/patch/kdenlive-0007-assetparametermodel-rows-indexOf-in-loops.patch new file mode 100644 index 000000000..e07b28fe9 --- /dev/null +++ b/defects/kdenlive/patch/kdenlive-0007-assetparametermodel-rows-indexOf-in-loops.patch @@ -0,0 +1,55 @@ +# UNDF: (leave blank) +# Defect: kdenlive-0007 +# Component: src/assets/model/assetparametermodel.hpp + assetparametermodel.cpp +# Pattern: CWE-407 — m_rows QVector with indexOf() called inside loops over m_params/m_fixedParams +# Severity: MEDIUM — O(P*R) in getAllParameters, toJson, valueAsJson, setParameters +# Fix: Add QHash m_rowIndex shadow map, updated on m_rows changes, for O(1) name-to-row lookup +--- a/src/assets/model/assetparametermodel.hpp ++++ b/src/assets/model/assetparametermodel.hpp +@@ member section + QVector m_rows; ++ QHash m_rowIndex; // shadow map: param name → row index for O(1) lookup + +--- a/src/assets/model/assetparametermodel.cpp ++++ b/src/assets/model/assetparametermodel.cpp + // Wherever m_rows is modified (append, insert, clear), also update m_rowIndex: + // m_rows.append(name); + // m_rowIndex.insert(name, m_rows.size() - 1); + // + // Then replace all m_rows.indexOf(name) with m_rowIndex.value(name, -1): + // + // Line ~611 (setParameter): +-// paramIndex = index(m_rows.indexOf(name), 0); ++// paramIndex = index(m_rowIndex.value(name, -1), 0); + // + // Line ~1234 (getAllParameters loop): +-// QModelIndex ix = index(m_rows.indexOf(param.first), 0); ++// QModelIndex ix = index(m_rowIndex.value(param.first, -1), 0); + // + // Line ~1320 (toJson fixedParams loop): +-// QModelIndex ix = index(m_rows.indexOf(fixed.first), 0); ++// QModelIndex ix = index(m_rowIndex.value(fixed.first, -1), 0); + // + // Line ~1362 (toJson params loop): +-// QModelIndex ix = index(m_rows.indexOf(param.first), 0); ++// QModelIndex ix = index(m_rowIndex.value(param.first, -1), 0); + // + // Line ~1492 (valueAsJson fixedParams loop): +-// QModelIndex ix = index(m_rows.indexOf(fixed.first), 0); ++// QModelIndex ix = index(m_rowIndex.value(fixed.first, -1), 0); + // + // Line ~1521 (valueAsJson params loop): +-// QModelIndex ix = index(m_rows.indexOf(param.first), 0); ++// QModelIndex ix = index(m_rowIndex.value(param.first, -1), 0); + // + // Line ~1797 (setParameters loop): +-// QModelIndex ix = index(m_rows.indexOf(param.first), 0); ++// QModelIndex ix = index(m_rowIndex.value(param.first, -1), 0); + // + // Line ~1878 (getParamIndex): +-// QModelIndex ix = index(m_rows.indexOf(paramName), 0); ++// QModelIndex ix = index(m_rowIndex.value(paramName, -1), 0); + // + // Line ~1887 (getParamFromName): +-// return index(m_rows.indexOf(paramName), 0); ++// return index(m_rowIndex.value(paramName, -1), 0); diff --git a/defects/kdenlive/patch/kdenlive-0008-urllistparamwidget-addItemsInSameFolder-linear-find.patch b/defects/kdenlive/patch/kdenlive-0008-urllistparamwidget-addItemsInSameFolder-linear-find.patch new file mode 100644 index 000000000..10f8d7748 --- /dev/null +++ b/defects/kdenlive/patch/kdenlive-0008-urllistparamwidget-addItemsInSameFolder-linear-find.patch @@ -0,0 +1,36 @@ +# UNDF: (leave blank) +# Defect: kdenlive-0008 +# Component: src/assets/view/widgets/urllistparamwidget.cpp +# Pattern: CWE-407 — std::find iterating QMap values in loop over directory entries +# Severity: MEDIUM — O(E*M) where E=directory entries, M=existing map values +# Fix: Build QSet of existing values before the loop for O(1) lookup +--- a/src/assets/view/widgets/urllistparamwidget.cpp ++++ b/src/assets/view/widgets/urllistparamwidget.cpp +@@ addItemsInSameFolder + void UrlListParamWidget::addItemsInSameFolder(const QString currentValue, QMap *listValues) + { + QDir dir = QFileInfo(currentValue).absoluteDir(); + if (dir.exists()) { ++ // Build a set of existing values for O(1) membership tests ++ QSet existingValues; ++ for (auto it = listValues->cbegin(); it != listValues->cend(); ++it) { ++ existingValues.insert(it.value()); ++ } + QStringList entries = dir.entryList(m_fileExt, QDir::Files); + for (const auto &filename : std::as_const(entries)) { + const QString path = dir.absoluteFilePath(filename); +- if (std::find((*listValues).cbegin(), (*listValues).cend(), path) == (*listValues).cend()) { ++ if (!existingValues.contains(path)) { + (*listValues).insert(QFileInfo(filename).baseName(), path); ++ existingValues.insert(path); + } + } + // make sure the current value is added. If it is a duplicate we remove it later +- if (std::find((*listValues).cbegin(), (*listValues).cend(), currentValue) == (*listValues).cend()) { ++ if (!existingValues.contains(currentValue)) { + if (QFileInfo::exists(currentValue)) { + (*listValues).insert(QFileInfo(currentValue).baseName(), currentValue); + } + } + } + } diff --git a/defects/kdenlive/unit/KdenliveTest.class b/defects/kdenlive/unit/KdenliveTest.class index 46bdd5f980e8900dae21a93165fb54d53449950d..843527956c0e171fd1a5e2f81414a968abb24e57 100644 GIT binary patch literal 9093 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 diff --git a/defects/kdenlive/unit/KdenliveTest.java b/defects/kdenlive/unit/KdenliveTest.java index 4452b8d19..90ba14a69 100644 --- a/defects/kdenlive/unit/KdenliveTest.java +++ b/defects/kdenlive/unit/KdenliveTest.java @@ -6,6 +6,10 @@ import java.util.*; * kdenlive-0002: TimelineModel clipIds vector linear find in mix loop * kdenlive-0003: TimelineController sorted_clips vector linear find in moveGroup * kdenlive-0004: TimelineModel all_items list linear find in resize + * kdenlive-0005: PreviewManager m_renderedChunks/m_dirtyChunks QVariantList linear contains + * kdenlive-0006: TimelineController canceled guides vector linear find + * kdenlive-0007: AssetParameterModel m_rows indexOf in parameter loops + * kdenlive-0008: UrlListParamWidget addItemsInSameFolder linear find on map values */ public class KdenliveTest { @@ -152,6 +156,145 @@ public class KdenliveTest { return ops; } + // --- kdenlive-0005: PreviewManager chunk lists linear contains --- + + static long previewChunksDefect(int numDirty, int numNew) { + // Simulates m_dirtyChunks as QVariantList with .contains() dedup + List dirtyChunks = new ArrayList<>(); + for (int i = 0; i < numDirty; i++) dirtyChunks.add(i * 25); + List renderedChunks = new ArrayList<>(); + for (int i = 0; i < numDirty / 2; i++) renderedChunks.add(i * 25); + long ops = 0; + // reloadChunks pattern: loop over new chunks, .contains() on existing + for (int i = 0; i < numNew; i++) { + int frame = i * 25; + // m_dirtyChunks.contains(frame) - linear scan + for (int existing : dirtyChunks) { + ops++; + if (existing == frame) break; + } + // m_renderedChunks.contains(frame) - linear scan + for (int existing : renderedChunks) { + ops++; + if (existing == frame) break; + } + } + return ops; + } + + static long previewChunksFixed(int numDirty, int numNew) { + Set dirtySet = new HashSet<>(); + for (int i = 0; i < numDirty; i++) dirtySet.add(i * 25); + Set renderedSet = new HashSet<>(); + for (int i = 0; i < numDirty / 2; i++) renderedSet.add(i * 25); + long ops = 0; + for (int i = 0; i < numNew; i++) { + int frame = i * 25; + ops++; // O(1) HashSet.contains for dirty + dirtySet.contains(frame); + ops++; // O(1) HashSet.contains for rendered + renderedSet.contains(frame); + } + return ops; + } + + // --- kdenlive-0006: canceled guide filter --- + + static long canceledGuidesDefect(int numGuides, int numCanceled) { + List guides = new ArrayList<>(); + for (int i = 0; i < numGuides; i++) guides.add(i * 30); + List canceled = new ArrayList<>(); + for (int i = 0; i < numCanceled; i++) canceled.add(i * 60); // every other guide + long ops = 0; + for (int guidePos : guides) { + // std::find on canceled vector + for (int c : canceled) { + ops++; + if (c == guidePos) break; + } + } + return ops; + } + + static long canceledGuidesFixed(int numGuides, int numCanceled) { + List guides = new ArrayList<>(); + for (int i = 0; i < numGuides; i++) guides.add(i * 30); + Set canceledSet = new HashSet<>(); + for (int i = 0; i < numCanceled; i++) canceledSet.add(i * 60); + long ops = 0; + for (int guidePos : guides) { + ops++; // O(1) HashSet lookup + canceledSet.contains(guidePos); + } + return ops; + } + + // --- kdenlive-0007: AssetParameterModel m_rows indexOf in loops --- + + static long rowsIndexOfDefect(int numParams) { + // m_rows is QVector, indexOf called per param + List rows = new ArrayList<>(); + for (int i = 0; i < numParams; i++) rows.add("param_" + i); + long ops = 0; + // Simulates getAllParameters/toJson loop + for (int i = 0; i < numParams; i++) { + String name = "param_" + i; + // indexOf: linear scan of m_rows + for (int j = 0; j < rows.size(); j++) { + ops++; + if (rows.get(j).equals(name)) break; + } + } + return ops; + } + + static long rowsIndexOfFixed(int numParams) { + Map rowIndex = new HashMap<>(); + for (int i = 0; i < numParams; i++) rowIndex.put("param_" + i, i); + long ops = 0; + for (int i = 0; i < numParams; i++) { + ops++; // O(1) HashMap lookup + rowIndex.get("param_" + i); + } + return ops; + } + + // --- kdenlive-0008: UrlListParamWidget addItemsInSameFolder --- + + static long urlListFindDefect(int numEntries, int numExisting) { + // QMap values iterated with std::find for each directory entry + Map listValues = new LinkedHashMap<>(); + for (int i = 0; i < numExisting; i++) { + listValues.put("file_" + i, "/path/to/file_" + i + ".png"); + } + long ops = 0; + for (int i = 0; i < numEntries; i++) { + String path = "/dir/entry_" + i + ".png"; + // std::find iterates all map values + for (String val : listValues.values()) { + ops++; + if (val.equals(path)) break; + } + } + return ops; + } + + static long urlListFindFixed(int numEntries, int numExisting) { + Map listValues = new LinkedHashMap<>(); + Set existingValues = new HashSet<>(); + for (int i = 0; i < numExisting; i++) { + String path = "/path/to/file_" + i + ".png"; + listValues.put("file_" + i, path); + existingValues.add(path); + } + long ops = 0; + for (int i = 0; i < numEntries; i++) { + ops++; // O(1) HashSet lookup + existingValues.contains("/dir/entry_" + i + ".png"); + } + return ops; + } + public static void main(String[] args) { int pass = 0, fail = 0; @@ -199,6 +342,50 @@ public class KdenliveTest { if (ok) pass++; else fail++; } + // Test kdenlive-0005: PreviewManager chunks (500 dirty, 500 new) + { + long defectOps = previewChunksDefect(500, 500); + long fixedOps = previewChunksFixed(500, 500); + double ratio = (double) defectOps / fixedOps; + boolean ok = ratio > 50.0; + System.out.printf("kdenlive-0005 PreviewManager chunk contains: defect=%d fixed=%d ratio=%.1fx %s%n", + defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL"); + if (ok) pass++; else fail++; + } + + // Test kdenlive-0006: canceled guides (500 guides, 200 canceled) + { + long defectOps = canceledGuidesDefect(500, 200); + long fixedOps = canceledGuidesFixed(500, 200); + double ratio = (double) defectOps / fixedOps; + boolean ok = ratio > 50.0; + System.out.printf("kdenlive-0006 canceled guides linear find: defect=%d fixed=%d ratio=%.1fx %s%n", + defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL"); + if (ok) pass++; else fail++; + } + + // Test kdenlive-0007: m_rows indexOf (50 params) + { + long defectOps = rowsIndexOfDefect(50); + long fixedOps = rowsIndexOfFixed(50); + double ratio = (double) defectOps / fixedOps; + boolean ok = ratio > 10.0; + System.out.printf("kdenlive-0007 m_rows indexOf in loop: defect=%d fixed=%d ratio=%.1fx %s%n", + defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL"); + if (ok) pass++; else fail++; + } + + // Test kdenlive-0008: urllist addItemsInSameFolder (300 entries, 200 existing) + { + long defectOps = urlListFindDefect(300, 200); + long fixedOps = urlListFindFixed(300, 200); + double ratio = (double) defectOps / fixedOps; + boolean ok = ratio > 50.0; + System.out.printf("kdenlive-0008 urllist value find: defect=%d fixed=%d ratio=%.1fx %s%n", + defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL"); + if (ok) pass++; else fail++; + } + System.out.printf("%nSummary: %d/%d PASS%n", pass, pass + fail); if (fail > 0) System.exit(1); }