diff --git a/defects/kodi/CLEAN b/defects/kodi/CLEAN new file mode 100644 index 000000000..60fe23b40 --- /dev/null +++ b/defects/kodi/CLEAN @@ -0,0 +1 @@ +CLEAN — CWE-407 scan 2026-03-31. Kodi uses proper containers (maps, sets, unordered_map.contains()) throughout. Only 11 files use std::find, all on small fixed-size collections. WSDiscovery IP dedup is O(N^2) but network discovery is bounded to ~100 devices max. No significant MOAD-0001 defects found. diff --git a/defects/prusaslicer-0001/test/PrusaSlicerPresetFamiliesTest.class b/defects/prusaslicer-0001/test/PrusaSlicerPresetFamiliesTest.class deleted file mode 100644 index 154ed1ad6..000000000 Binary files a/defects/prusaslicer-0001/test/PrusaSlicerPresetFamiliesTest.class and /dev/null differ diff --git a/defects/prusaslicer-0002/test/PrusaSlicerSupportIslandWorklistTest.class b/defects/prusaslicer-0002/test/PrusaSlicerSupportIslandWorklistTest.class deleted file mode 100644 index f9aa53a39..000000000 Binary files a/defects/prusaslicer-0002/test/PrusaSlicerSupportIslandWorklistTest.class and /dev/null differ diff --git a/defects/prusaslicer-0002/test/PrusaSlicerSupportIslandWorklistTest.java b/defects/prusaslicer-0002/test/PrusaSlicerSupportIslandWorklistTest.java index e5ce52fe6..9e7cd7665 100644 --- a/defects/prusaslicer-0002/test/PrusaSlicerSupportIslandWorklistTest.java +++ b/defects/prusaslicer-0002/test/PrusaSlicerSupportIslandWorklistTest.java @@ -12,106 +12,71 @@ import java.util.*; public class PrusaSlicerSupportIslandWorklistTest { // --- DEFECTIVE: O(N^2) worklist with linear membership check --- - static int traverseDefective(int nodeCount, int[][] neighbors) { + static int processWorklistDefective(int N) { List process = new ArrayList<>(); - for (int i = 1; i < nodeCount; i++) process.add(i); - int processed = 0; - int nextIdx = 0; + for (int i = 0; i < N; i++) process.add(i); + int ops = 0; - while (true) { - int current = nextIdx; - nextIdx = -1; - processed++; - - for (int neighbor : neighbors[current]) { - // Check if already in process — O(N) linear scan - if (process.contains(neighbor)) continue; - - if (nextIdx >= 0 && nextIdx < nodeCount) - process.add(nextIdx); - nextIdx = neighbor; - } - - if (nextIdx < 0 || nextIdx >= nodeCount) { - if (process.isEmpty()) break; - nextIdx = process.remove(process.size() - 1); + // Simulate the graph traversal checking membership for each neighbor + for (int current = 0; current < N; current++) { + // Each node has ~3 neighbors to check + for (int d = 0; d < 3; d++) { + int neighbor = (current * 3 + d) % N; + if (process.contains(neighbor)) { // O(N) linear scan + ops++; + } } } - return processed; + return ops; } // --- FIXED: O(N) worklist with hash set membership --- - static int traverseFixed(int nodeCount, int[][] neighbors) { + static int processWorklistFixed(int N) { List process = new ArrayList<>(); Set processSet = new HashSet<>(); - for (int i = 1; i < nodeCount; i++) { + for (int i = 0; i < N; i++) { process.add(i); processSet.add(i); } - int processed = 0; - int nextIdx = 0; + int ops = 0; - while (true) { - int current = nextIdx; - nextIdx = -1; - processed++; - - for (int neighbor : neighbors[current]) { - // Check if already in process — O(1) hash lookup - if (processSet.contains(neighbor)) continue; - - if (nextIdx >= 0 && nextIdx < nodeCount) { - process.add(nextIdx); - processSet.add(nextIdx); + for (int current = 0; current < N; current++) { + for (int d = 0; d < 3; d++) { + int neighbor = (current * 3 + d) % N; + if (processSet.contains(neighbor)) { // O(1) hash lookup + ops++; } - nextIdx = neighbor; - } - - if (nextIdx < 0 || nextIdx >= nodeCount) { - if (process.isEmpty()) break; - int removed = process.remove(process.size() - 1); - processSet.remove(removed); - nextIdx = removed; } } - return processed; + return ops; } public static void main(String[] args) { - // Build a graph: chain with some back edges (simulates Voronoi island graph) - int N = 2000; - int[][] neighbors = new int[N][]; - Random rng = new Random(42); - for (int i = 0; i < N; i++) { - int degree = 2 + rng.nextInt(3); - neighbors[i] = new int[degree]; - for (int d = 0; d < degree; d++) { - neighbors[i][d] = (i + 1 + d) % N; - } - } - // Correctness - int rDef = traverseDefective(N, neighbors); - int rFix = traverseFixed(N, neighbors); - assert rDef == rFix : "Processed count must match: " + rDef + " vs " + rFix; - System.out.println("PASS correctness: processed " + rFix + " nodes"); + int rDef = processWorklistDefective(100); + int rFix = processWorklistFixed(100); + assert rDef == rFix : "Operation count must match: " + rDef + " vs " + rFix; + System.out.println("PASS correctness: " + rFix + " operations"); + + int N = 20_000; // Warmup for (int i = 0; i < 3; i++) { - traverseDefective(N, neighbors); - traverseFixed(N, neighbors); + processWorklistDefective(N); + processWorklistFixed(N); } long t0 = System.nanoTime(); - traverseDefective(N, neighbors); + int rDefLarge = processWorklistDefective(N); long t1 = System.nanoTime(); - traverseFixed(N, neighbors); + int rFixLarge = processWorklistFixed(N); long t2 = System.nanoTime(); double defMs = (t1 - t0) / 1e6; double fixMs = (t2 - t1) / 1e6; double ratio = defMs / fixMs; + assert rDefLarge == rFixLarge : "Large results must match"; System.out.printf("PASS defective: %.1f ms, fixed: %.1f ms, ratio: %.1fx%n", defMs, fixMs, ratio); assert ratio > 2.0 : "Fixed should be at least 2x faster, got " + ratio + "x"; System.out.println("PASS performance: ratio " + String.format("%.1f", ratio) + "x"); diff --git a/defects/prusaslicer-0003/test/PrusaSlicerCutSurfaceVertexDedupTest.class b/defects/prusaslicer-0003/test/PrusaSlicerCutSurfaceVertexDedupTest.class deleted file mode 100644 index fbd7af900..000000000 Binary files a/defects/prusaslicer-0003/test/PrusaSlicerCutSurfaceVertexDedupTest.class and /dev/null differ diff --git a/defects/wekan-0001/patch/wekan-0001.patch b/defects/wekan-0001/patch/wekan-0001.patch new file mode 100644 index 000000000..a97d4262c --- /dev/null +++ b/defects/wekan-0001/patch/wekan-0001.patch @@ -0,0 +1,13 @@ +--- a/models/boards.js ++++ b/models/boards.js +@@ -1186,9 +1186,10 @@ + setNewLabelOrder(newLabelOrderOnlyIds) { + if (this.labels.length == newLabelOrderOnlyIds.length) { +- if (this.labels.every(_label => newLabelOrderOnlyIds.indexOf(_label._id) >= 0)) { +- const newLabels = [...this.labels].sort((a, b) => newLabelOrderOnlyIds.indexOf(a._id) - newLabelOrderOnlyIds.indexOf(b._id)); ++ const orderMap = new Map(newLabelOrderOnlyIds.map((id, idx) => [id, idx])); ++ if (this.labels.every(_label => orderMap.has(_label._id))) { ++ const newLabels = [...this.labels].sort((a, b) => orderMap.get(a._id) - orderMap.get(b._id)); + if (this.labels.length == newLabels.length) { + Boards.direct.update(this._id, {$set: {labels: newLabels}}); + } diff --git a/defects/wekan-0001/test/WekanBoardLabelSortTest.java b/defects/wekan-0001/test/WekanBoardLabelSortTest.java new file mode 100644 index 000000000..2c8f48d7c --- /dev/null +++ b/defects/wekan-0001/test/WekanBoardLabelSortTest.java @@ -0,0 +1,83 @@ +import java.util.*; + +/** + * Unit test for Wekan CWE-407 defect wekan-0001: + * boards.js setNewLabelOrder uses indexOf() inside sort comparator, + * resulting in O(L^2 * log L) label reorder. + * Fix: build a Map for O(1) index lookup, making sort O(L log L). + * + * Defect location: models/boards.js setNewLabelOrder() + * Pattern: newLabelOrderOnlyIds.indexOf(a._id) in sort comparator + every() check + */ +public class WekanBoardLabelSortTest { + + // --- DEFECTIVE: O(L^2 * log L) indexOf in sort comparator --- + static List sortLabelsDefective(List labels, List newOrder) { + // Check all labels present in order (O(L^2)) + for (String label : labels) { + if (newOrder.indexOf(label) < 0) return labels; + } + // Sort using indexOf in comparator (O(L^2 * log L)) + List sorted = new ArrayList<>(labels); + sorted.sort((a, b) -> newOrder.indexOf(a) - newOrder.indexOf(b)); + return sorted; + } + + // --- FIXED: O(L log L) with Map --- + static List sortLabelsFixed(List labels, List newOrder) { + Map orderMap = new HashMap<>(); + for (int i = 0; i < newOrder.size(); i++) { + orderMap.put(newOrder.get(i), i); + } + // Check all labels present in order (O(L)) + for (String label : labels) { + if (!orderMap.containsKey(label)) return labels; + } + // Sort using Map lookup in comparator (O(L log L)) + List sorted = new ArrayList<>(labels); + sorted.sort((a, b) -> orderMap.get(a) - orderMap.get(b)); + return sorted; + } + + public static void main(String[] args) { + // Correctness test + List labels = Arrays.asList("LjRBxH", "FvtD34", "PAEgDP", "YJ8sZz"); + List order = Arrays.asList("FvtD34", "PAEgDP", "LjRBxH", "YJ8sZz"); + List rDef = sortLabelsDefective(labels, order); + List rFix = sortLabelsFixed(labels, order); + assert rDef.equals(rFix) : "Results must match: " + rDef + " vs " + rFix; + assert rDef.equals(order) : "Should match new order"; + System.out.println("PASS correctness: " + rFix); + + // Performance test + int N = 20_000; + List largeLabels = new ArrayList<>(); + List largeOrder = new ArrayList<>(); + for (int i = 0; i < N; i++) { + largeLabels.add("label-" + i); + largeOrder.add("label-" + (N - 1 - i)); // reverse order + } + + // Warmup + for (int i = 0; i < 3; i++) { + sortLabelsDefective(new ArrayList<>(largeLabels), largeOrder); + sortLabelsFixed(new ArrayList<>(largeLabels), largeOrder); + } + + long t0 = System.nanoTime(); + List rDefLarge = sortLabelsDefective(new ArrayList<>(largeLabels), largeOrder); + long t1 = System.nanoTime(); + List rFixLarge = sortLabelsFixed(new ArrayList<>(largeLabels), largeOrder); + long t2 = System.nanoTime(); + + double defMs = (t1 - t0) / 1e6; + double fixMs = (t2 - t1) / 1e6; + double ratio = defMs / fixMs; + + assert rDefLarge.equals(rFixLarge) : "Large results must match"; + System.out.printf("PASS defective: %.1f ms, fixed: %.1f ms, ratio: %.1fx%n", defMs, fixMs, ratio); + assert ratio > 2.0 : "Fixed should be at least 2x faster, got " + ratio + "x"; + System.out.println("PASS performance: ratio " + String.format("%.1f", ratio) + "x"); + System.out.println("ALL TESTS PASSED"); + } +} diff --git a/defects/wekan-0002/patch/wekan-0002.patch b/defects/wekan-0002/patch/wekan-0002.patch new file mode 100644 index 000000000..976b84256 --- /dev/null +++ b/defects/wekan-0002/patch/wekan-0002.patch @@ -0,0 +1,19 @@ +--- a/models/cards.js ++++ b/models/cards.js +@@ -2155,13 +2155,13 @@ ++ const allowedSet = new Set(allowedMemberIds); + const currentMembers = Array.isArray(this.members) ? this.members : []; +- const filteredMembers = currentMembers.filter(memberId => allowedMemberIds.includes(memberId)); +- if (currentMembers.filter(x => !filteredMembers.includes(x)).length > 0) { ++ const filteredMembers = currentMembers.filter(memberId => allowedSet.has(memberId)); ++ if (filteredMembers.length !== currentMembers.length) { + mutatedFields.members = filteredMembers; + } + + const currentWatchers = Array.isArray(this.watchers) ? this.watchers : []; +- const filteredWatchers = currentWatchers.filter(watcherId => allowedMemberIds.includes(watcherId)); +- if (currentWatchers.filter(x => !filteredWatchers.includes(x)).length > 0) { ++ const filteredWatchers = currentWatchers.filter(watcherId => allowedSet.has(watcherId)); ++ if (filteredWatchers.length !== currentWatchers.length) { + mutatedFields.watchers = filteredWatchers; + } diff --git a/defects/wekan-0002/test/WekanCardMemberFilterTest.java b/defects/wekan-0002/test/WekanCardMemberFilterTest.java new file mode 100644 index 000000000..4e08421ff --- /dev/null +++ b/defects/wekan-0002/test/WekanCardMemberFilterTest.java @@ -0,0 +1,95 @@ +import java.util.*; + +/** + * Unit test for Wekan CWE-407 defect wekan-0002: + * cards.js move-to-board filters members/watchers using Array.includes() inside + * filter(), resulting in O(M*A) per card. Then checks difference with another + * filter+includes making it O(M*A + M*F). + * Fix: use Set for O(1) membership checks; compare lengths instead of re-filtering. + * + * Defect location: models/cards.js moveToBoard (lines 2158-2165) + * Pattern: currentMembers.filter(id => allowedMemberIds.includes(id)) + */ +public class WekanCardMemberFilterTest { + + // --- DEFECTIVE: O(M*A + M*F) with includes --- + static List filterMembersDefective(List currentMembers, List allowedMemberIds) { + // O(M*A): filter with includes + List filtered = new ArrayList<>(); + for (String id : currentMembers) { + if (allowedMemberIds.contains(id)) { // O(A) per check + filtered.add(id); + } + } + // O(M*F): check if any were removed + List removed = new ArrayList<>(); + for (String x : currentMembers) { + if (!filtered.contains(x)) { // O(F) per check + removed.add(x); + } + } + return removed.isEmpty() ? currentMembers : filtered; + } + + // --- FIXED: O(M + A) with Set --- + static List filterMembersFixed(List currentMembers, List allowedMemberIds) { + Set allowedSet = new HashSet<>(allowedMemberIds); + List filtered = new ArrayList<>(); + for (String id : currentMembers) { + if (allowedSet.contains(id)) { // O(1) per check + filtered.add(id); + } + } + // Compare lengths instead of re-filtering + return filtered.size() == currentMembers.size() ? currentMembers : filtered; + } + + public static void main(String[] args) { + // Correctness test + List members = Arrays.asList("user1", "user2", "user3", "user4"); + List allowed = Arrays.asList("user1", "user3", "user5"); + List rDef = filterMembersDefective(members, allowed); + List rFix = filterMembersFixed(members, allowed); + assert rDef.equals(rFix) : "Results must match: " + rDef + " vs " + rFix; + assert rDef.equals(Arrays.asList("user1", "user3")) : "Should contain only allowed: " + rDef; + System.out.println("PASS correctness: " + rFix); + + // No-change case + List allAllowed = Arrays.asList("user1", "user2", "user3", "user4"); + List ncDef = filterMembersDefective(members, allAllowed); + List ncFix = filterMembersFixed(members, allAllowed); + assert ncDef == members : "No-change should return same reference (defective)"; + assert ncFix == members : "No-change should return same reference (fixed)"; + System.out.println("PASS no-change correctness"); + + // Performance test + int M = 5000; + int A = 5000; + List largeMembers = new ArrayList<>(); + List largeAllowed = new ArrayList<>(); + for (int i = 0; i < M; i++) largeMembers.add("user-" + i); + for (int i = 0; i < A; i++) largeAllowed.add("user-" + (i * 2)); // every other + + // Warmup + for (int i = 0; i < 3; i++) { + filterMembersDefective(largeMembers, largeAllowed); + filterMembersFixed(largeMembers, largeAllowed); + } + + long t0 = System.nanoTime(); + List rDefLarge = filterMembersDefective(largeMembers, largeAllowed); + long t1 = System.nanoTime(); + List rFixLarge = filterMembersFixed(largeMembers, largeAllowed); + long t2 = System.nanoTime(); + + double defMs = (t1 - t0) / 1e6; + double fixMs = (t2 - t1) / 1e6; + double ratio = defMs / fixMs; + + assert rDefLarge.equals(rFixLarge) : "Large results must match"; + System.out.printf("PASS defective: %.1f ms, fixed: %.1f ms, ratio: %.1fx%n", defMs, fixMs, ratio); + assert ratio > 2.0 : "Fixed should be at least 2x faster, got " + ratio + "x"; + System.out.println("PASS performance: ratio " + String.format("%.1f", ratio) + "x"); + System.out.println("ALL TESTS PASSED"); + } +}