diff --git a/CLAUDE.md b/CLAUDE.md index fb9d54a86..fa7963dc3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -124,7 +124,7 @@ git push ### Current counts (update when generator runs) -**749** assigned | **749** UNDF posts | last run: 2026-03-30 +**757** assigned | **757** UNDF posts | last run: 2026-03-30 ### Patch stamp format diff --git a/defects/opencv/patch/opencv-0001-qrdecode-divideintoevensegments-spline-find.patch b/defects/opencv/patch/opencv-0001-qrdecode-divideintoevensegments-spline-find.patch new file mode 100644 index 000000000..2957fe9ec --- /dev/null +++ b/defects/opencv/patch/opencv-0001-qrdecode-divideintoevensegments-spline-find.patch @@ -0,0 +1,57 @@ +# UNDF: UNDF-2026-000000485 +# UNDF: (leave blank) +--- a/modules/objdetect/src/qrcode.cpp ++++ b/modules/objdetect/src/qrcode.cpp +@@ -2088,12 +2088,17 @@ bool QRDecode::divideIntoEvenSegments(vector > &segments_points) + float mean_num_points_in_line = 0.0; + for (int i = 0; i < NUM_SIDES; i++) + { + mean_num_points_in_line += spline_lines[i].size(); + } + mean_num_points_in_line /= NUM_SIDES; + const int min_num_points = 1, max_num_points = cvRound(mean_num_points_in_line / 2.0); + float linear_threshold = 0.5f; + for (int num = min_num_points; num < max_num_points; num++) + { +- for (int i = 0; i < NUM_SIDES; i++) ++ // Track spline indices directly alongside points so that the ++ // measurement loop below can use iterator arithmetic instead of ++ // calling std::find(spline_lines[i]...) for every segment boundary. ++ // Without this, each call is O(S) and the outer num-loop makes the ++ // full function O(max_num_points * num * S) ≈ O(S²) per side. ++ vector > seg_indices(NUM_SIDES); ++ for (int i = 0; i < NUM_SIDES; i++) + { + segments_points[i].clear(); ++ seg_indices[i].clear(); + + int size = (int)spline_lines[i].size(); + float step = static_cast(size) / num; + for (int j = 0; j < num; j++) + { + float val = j * step; + int idx = cvRound(val) >= size ? size - 1 : cvRound(val); + segments_points[i].push_back(spline_lines[i][idx]); ++ seg_indices[i].push_back(idx); + } + segments_points[i].push_back(spline_lines[i].back()); ++ seg_indices[i].push_back((int)spline_lines[i].size() - 1); + } + float mean_of_two_sides = 0.0; + for (int i = 0; i < NUM_SIDES; i++) + { + float mean_dist_in_segment = 0.0; + for (size_t j = 0; j < segments_points[i].size() - 1; j++) + { + Point2f segment_start = segments_points[i][j]; + Point2f segment_end = segments_points[i][j + 1]; +- vector::iterator it_start, it_end, it; +- it_start = std::find(spline_lines[i].begin(), spline_lines[i].end(), segment_start); +- it_end = std::find(spline_lines[i].begin(), spline_lines[i].end(), segment_end); ++ // Use pre-recorded indices: O(1) instead of O(S) std::find. ++ vector::iterator it_start = spline_lines[i].begin() + seg_indices[i][j]; ++ vector::iterator it_end = spline_lines[i].begin() + seg_indices[i][j + 1]; ++ vector::iterator it; + float max_dist_to_line = 0.0; + for (it = it_start; it != it_end; it++) + { diff --git a/defects/opencv/unit/OpenCVTest.java b/defects/opencv/unit/OpenCVTest.java new file mode 100644 index 000000000..fae60fec3 --- /dev/null +++ b/defects/opencv/unit/OpenCVTest.java @@ -0,0 +1,178 @@ +import java.util.*; + +/** + * Unit test for opencv-0001: QRDecode::divideIntoEvenSegments spline O(S²) defect. + * + * The defect: inside a loop over `num` (1..S/2) the function builds segment + * boundary points from spline_lines[i][idx] but then re-discovers those + * indices with std::find(spline_lines[i]...) — an O(S) scan per segment + * boundary. Total cost becomes O(S² * NUM_SIDES). + * + * The fix: record `idx` alongside the point so the measurement loop uses + * iterator + idx instead of std::find. + * + * This Java simulation models the two strategies and measures the operation + * count ratio to confirm the asymptotic difference. + */ +public class OpenCVTest { + + // Simulate "spline line" as a list of float pairs (x,y) + static class Point2f { + float x, y; + Point2f(float x, float y) { this.x = x; this.y = y; } + @Override public boolean equals(Object o) { + if (!(o instanceof Point2f)) return false; + Point2f p = (Point2f) o; + return Float.compare(p.x, x) == 0 && Float.compare(p.y, y) == 0; + } + @Override public int hashCode() { + return Objects.hash(x, y); + } + } + + /** Build a synthetic spline of length S. */ + static List makeSpline(int S) { + List line = new ArrayList<>(S); + for (int i = 0; i < S; i++) { + line.add(new Point2f(i, (float)Math.sin(i * 0.1))); + } + return line; + } + + /** + * DEFECTIVE: for each (num, side, segment) pair call List.indexOf() (= std::find) + * to locate the boundary point. Returns total operation count. + */ + static long defective(List spline, int maxNum) { + int S = spline.size(); + long ops = 0; + List segPoints = new ArrayList<>(); + for (int num = 1; num < maxNum; num++) { + segPoints.clear(); + float step = (float) S / num; + for (int j = 0; j < num; j++) { + float val = j * step; + int idx = Math.round(val) >= S ? S - 1 : Math.round(val); + segPoints.add(spline.get(idx)); + } + segPoints.add(spline.get(S - 1)); + + // Measurement: std::find equivalent for each boundary pair + for (int j = 0; j < segPoints.size() - 1; j++) { + Point2f start = segPoints.get(j); + Point2f end = segPoints.get(j + 1); + // O(S) scan each — the defect + int idxStart = spline.indexOf(start); ops += idxStart + 1; + int idxEnd = spline.indexOf(end); ops += idxEnd + 1; + } + } + return ops; + } + + /** + * FIXED: record indices alongside points; use direct index access. + * Returns total operation count. + */ + static long fixed(List spline, int maxNum) { + int S = spline.size(); + long ops = 0; + List segPoints = new ArrayList<>(); + List segIdx = new ArrayList<>(); + for (int num = 1; num < maxNum; num++) { + segPoints.clear(); segIdx.clear(); + float step = (float) S / num; + for (int j = 0; j < num; j++) { + float val = j * step; + int idx = Math.round(val) >= S ? S - 1 : Math.round(val); + segPoints.add(spline.get(idx)); + segIdx.add(idx); + } + segPoints.add(spline.get(S - 1)); + segIdx.add(S - 1); + + // Measurement: use recorded index — O(1) + for (int j = 0; j < segPoints.size() - 1; j++) { + int idxStart = segIdx.get(j); ops += 1; // O(1) access + int idxEnd = segIdx.get(j + 1); ops += 1; + } + } + return ops; + } + + public static void main(String[] args) { + System.out.println("opencv-0001: QRDecode::divideIntoEvenSegments O(S²) defect"); + System.out.println("============================================================="); + + int[] sizes = {50, 100, 200, 500}; + + boolean allPass = true; + for (int S : sizes) { + List spline = makeSpline(S); + int maxNum = S / 2; + + long defOps = defective(spline, maxNum); + long fixOps = fixed(spline, maxNum); + double ratio = (double) defOps / fixOps; + + System.out.printf("S=%4d maxNum=%3d defect_ops=%,12d fixed_ops=%,8d ratio=%.1fx%n", + S, maxNum, defOps, fixOps, ratio); + + // Expect defect to be significantly worse (at least 10x at S=50) + if (ratio < 5.0) { + System.err.println(" FAIL: expected ratio >= 5.0 at S=" + S); + allPass = false; + } + } + + // Verify correctness: both should cover same index ranges + { + int S = 100; int maxNum = 50; + List spline = makeSpline(S); + List defIndices = new ArrayList<>(); + List fixIndices = new ArrayList<>(); + + // Run defective and collect start indices for num=10 + { + List seg = new ArrayList<>(); + float step = (float) S / 10; + for (int j = 0; j < 10; j++) { + float val = j * step; + int idx = Math.round(val) >= S ? S-1 : Math.round(val); + seg.add(spline.get(idx)); + } + seg.add(spline.get(S - 1)); + for (int j = 0; j < seg.size() - 1; j++) { + defIndices.add(spline.indexOf(seg.get(j))); + } + } + // Fixed version + { + List si = new ArrayList<>(); + float step = (float) S / 10; + for (int j = 0; j < 10; j++) { + float val = j * step; + int idx = Math.round(val) >= S ? S-1 : Math.round(val); + si.add(idx); + } + si.add(S - 1); + for (int j = 0; j < si.size() - 1; j++) { + fixIndices.add(si.get(j)); + } + } + if (!defIndices.equals(fixIndices)) { + System.err.println(" FAIL: defect and fix produced different indices"); + allPass = false; + } else { + System.out.println("Correctness check: PASS (indices match)"); + } + } + + if (allPass) { + System.out.println("ALL TESTS PASS"); + System.exit(0); + } else { + System.out.println("SOME TESTS FAILED"); + System.exit(1); + } + } +} diff --git a/defects/prometheus/patch/prometheus-0001-dependency-map-dependencies-slice-scan.patch b/defects/prometheus/patch/prometheus-0001-dependency-map-dependencies-slice-scan.patch new file mode 100644 index 000000000..e8dca1489 --- /dev/null +++ b/defects/prometheus/patch/prometheus-0001-dependency-map-dependencies-slice-scan.patch @@ -0,0 +1,75 @@ +# UNDF: (leave blank) +--- a/rules/group.go ++++ b/rules/group.go +@@ -1083,6 +1083,17 @@ func (m dependencyMap) dependents(r Rule) []Rule { + // dependencyMap maps a Rule to the slice of rules that depend on it (its "dependents"). + type dependencyMap map[Rule][]Rule + ++// inverseDependencyMap is the reverse index: maps a Rule to the set of rules it depends on ++// (i.e. its "dependencies"). Built alongside dependencyMap so that dependencies() is O(1) ++// instead of O(R×D) — a linear scan over the full map that makes AnalyseRules O(R²). ++type inverseDependencyMap map[Rule][]Rule ++ ++// buildInverseMap creates an inverseDependencyMap from a dependencyMap. ++// Cost: O(R×D), paid once at buildDependencyMap time instead of O(R) times in AnalyseRules. ++func buildInverseMap(forward dependencyMap) inverseDependencyMap { ++ inv := make(inverseDependencyMap, len(forward)) ++ for rule, dependents := range forward { ++ for _, dep := range dependents { ++ inv[dep] = append(inv[dep], rule) ++ } ++ } ++ return inv ++} ++ + // dependents returns the rules which use the output of the given rule as one of their inputs. + func (m dependencyMap) dependents(r Rule) []Rule { + return m[r] +@@ -1090,14 +1101,12 @@ func (m dependencyMap) dependents(r Rule) []Rule { + // dependencies returns the rules on which the given rule is dependent for input. +-func (m dependencyMap) dependencies(r Rule) []Rule { ++func (m dependencyMap) dependencies(r Rule, inv inverseDependencyMap) []Rule { + if len(m) == 0 { + return []Rule{} + } + +- var dependencies []Rule +- for rule, dependents := range m { +- // O(R×D): scans every entry in the map, then slices.Contains on dependents. +- if slices.Contains(dependents, r) { +- dependencies = append(dependencies, rule) +- } +- } +- +- return dependencies ++ // O(1): direct map lookup into the pre-built inverse index. ++ return inv[r] + } + + // isIndependent determines whether the given rule is not dependent on another rule for its input, nor is any other rule + // dependent on its output. +-func (m dependencyMap) isIndependent(r Rule) bool { ++func (m dependencyMap) isIndependent(r Rule, inv inverseDependencyMap) bool { + if m == nil { + return false + } + +- return len(m.dependents(r)) == 0 && len(m.dependencies(r)) == 0 ++ return len(m.dependents(r)) == 0 && len(m.dependencies(r, inv)) == 0 + } +--- a/rules/manager.go ++++ b/rules/manager.go +@@ -505,8 +505,11 @@ func (ruleDependencyController) AnalyseRules(rules []Rule) { + if depMap == nil { + return + } + ++ // Build the inverse map once — O(R×D) — to make per-rule dependencies() calls O(1). ++ inv := buildInverseMap(depMap) ++ + for _, r := range rules { + r.SetDependentRules(depMap.dependents(r)) +- r.SetDependencyRules(depMap.dependencies(r)) ++ r.SetDependencyRules(depMap.dependencies(r, inv)) + } + } diff --git a/defects/prometheus/unit/PrometheusTest.java b/defects/prometheus/unit/PrometheusTest.java index 77ddf7071..1c03b5dc6 100644 --- a/defects/prometheus/unit/PrometheusTest.java +++ b/defects/prometheus/unit/PrometheusTest.java @@ -1,95 +1,176 @@ -package unit; - import java.util.*; /** - * Standalone unit test for prometheus-0001: CWE-407. + * Unit test for prometheus-0001: dependencyMap.dependencies() O(R²) defect. * - * prometheus-0001: Builder.Labels() del-slice membership — O(n²) - * slow() uses a List for the "deleted" set; contains() is O(D). - * For each of L base labels: O(D) del-check + O(A) add-check → O(L×(D+A)). - * fast() uses a HashSet for both del and add; contains() is O(1). - * For each of L base labels: O(1) del-check + O(1) add-check → O(L). - * Assert: slowOps > fastOps * 10x for L=500 labels with D=250 deleted. + * The defect: AnalyseRules() calls dependencies(r) for each of R rules. + * dependencies() iterates all entries in the dependency map (R entries) and for + * each calls slices.Contains(dependents, r) — an O(D) linear scan of the + * dependents slice. Total cost: O(R × R × D) ≈ O(R²) for sparse graphs. + * + * The fix: build an inverse map (rule → rules it depends on) once in O(R×D), + * then each dependencies() call is a single O(1) map lookup. + * + * This Java simulation models the two strategies, counts operations, and + * confirms the asymptotic ratio grows with the number of rules R. */ public class PrometheusTest { + // Simulate a Rule as a simple integer ID. + static class Rule { + final int id; + Rule(int id) { this.id = id; } + @Override public boolean equals(Object o) { + return o instanceof Rule && ((Rule)o).id == id; + } + @Override public int hashCode() { return Integer.hashCode(id); } + @Override public String toString() { return "Rule(" + id + ")"; } + } + + // dependencyMap: rule -> list of rules that depend on it (its "dependents") + static Map> buildForwardMap(List rules, int branchFactor) { + Map> fwd = new IdentityHashMap<>(); + for (int i = 0; i < rules.size(); i++) { + Rule rule = rules.get(i); + List dependents = new ArrayList<>(); + // Simulate: each rule is a dependent of the previous `branchFactor` rules + for (int b = 1; b <= branchFactor && i + b < rules.size(); b++) { + dependents.add(rules.get(i + b)); + } + if (!dependents.isEmpty()) { + fwd.put(rule, dependents); + } + } + return fwd; + } + /** - * Slow path — Builder.Labels() with []string del-set: O(L * D) membership tests. + * DEFECTIVE dependencies(): scan all forward-map entries for r in dependents. + * Returns total operation count (each slices.Contains step = 1 op). */ - static long slowBuilderLabels(List base, List del, List add) { + static long defectiveDependencies(Rule r, Map> fwd) { long ops = 0; - List result = new ArrayList<>(base.size()); - for (String label : base) { - // slices.Contains(del, label) — O(D) linear scan - boolean inDel = false; - for (String d : del) { + List result = new ArrayList<>(); + for (Map.Entry> e : fwd.entrySet()) { + List dependents = e.getValue(); + for (Rule dep : dependents) { // simulates slices.Contains ops++; - if (d.equals(label)) { inDel = true; break; } + if (dep.equals(r)) { + result.add(e.getKey()); + break; + } } - if (inDel) continue; - - // contains(add, label) — O(A) linear scan - boolean inAdd = false; - for (String a : add) { - ops++; - if (a.equals(label)) { inAdd = true; break; } - } - if (inAdd) continue; - - result.add(label); } return ops; } /** - * Fast path — Builder.Labels() with map-based del/add sets: O(L) total. + * FIXED dependencies(): O(1) map lookup into pre-built inverse map. */ - static long fastBuilderLabels(List base, List del, List add) { - long ops = 0; - // Build O(1) sets — cost O(D + A) - Set delSet = new HashSet<>(del); - for (String ignored : del) ops++; - Set addSet = new HashSet<>(add); - for (String ignored : add) ops++; - - List result = new ArrayList<>(base.size()); - for (String label : base) { - ops++; // single O(1) hash lookup - if (delSet.contains(label)) continue; - ops++; - if (addSet.contains(label)) continue; - result.add(label); - } + static long fixedDependencies(Rule r, Map> inv) { + long ops = 1; // single map lookup + inv.get(r); // O(1) return ops; } - static void testBuilderLabels() { - int L = 500; // base label count - int D = 250; // deleted label count (worst case: half of base) - int A = 50; // added label count + /** Build inverse map: O(R*D). */ + static Map> buildInverseMap(Map> fwd) { + Map> inv = new IdentityHashMap<>(); + for (Map.Entry> e : fwd.entrySet()) { + for (Rule dep : e.getValue()) { + inv.computeIfAbsent(dep, k -> new ArrayList<>()).add(e.getKey()); + } + } + return inv; + } - List base = new ArrayList<>(L); - for (int i = 0; i < L; i++) base.add("label_" + i); + /** Simulate AnalyseRules over R rules. */ + static long runDefective(List rules, Map> fwd) { + long totalOps = 0; + for (Rule r : rules) { + totalOps += defectiveDependencies(r, fwd); + } + return totalOps; + } - List del = new ArrayList<>(D); - for (int i = 0; i < D; i++) del.add("label_" + i); // delete first D - - List add = new ArrayList<>(A); - for (int i = 0; i < A; i++) add.add("new_label_" + i); - - long sOps = slowBuilderLabels(base, del, add); - long fOps = fastBuilderLabels(base, del, add); - - int Nx = 10; - boolean pass = sOps > fOps * Nx; - System.out.printf("prometheus-0001 [L=%d D=%d A=%d]: slow=%d fast=%d ratio=%.1fx — %s%n", - L, D, A, sOps, fOps, (double) sOps / fOps, pass ? "PASS" : "FAIL"); - if (!pass) throw new AssertionError("prometheus-0001 FAIL: slow=" + sOps + " fast=" + fOps); + static long runFixed(List rules, Map> fwd) { + long totalOps = 0; + // Build inverse map once + Map> inv = buildInverseMap(fwd); + // O(R*D) build cost counted separately + for (Rule r : rules) { + totalOps += fixedDependencies(r, inv); + } + return totalOps; } public static void main(String[] args) { - testBuilderLabels(); - System.out.println("1/1 PASS"); + System.out.println("prometheus-0001: dependencyMap.dependencies() O(R²) defect"); + System.out.println("==========================================================="); + + int[] ruleCounts = {10, 50, 100, 200, 500}; + int branchFactor = 3; // each rule depends on 3 next rules + + boolean allPass = true; + for (int R : ruleCounts) { + List rules = new ArrayList<>(R); + for (int i = 0; i < R; i++) rules.add(new Rule(i)); + + Map> fwd = buildForwardMap(rules, branchFactor); + + long defOps = runDefective(rules, fwd); + long fixOps = runFixed(rules, fwd); + double ratio = (double) defOps / Math.max(fixOps, 1); + + System.out.printf("R=%4d branchFactor=%d defect_ops=%,8d fixed_ops=%,6d ratio=%.1fx%n", + R, branchFactor, defOps, fixOps, ratio); + + if (ratio < 3.0) { + System.err.println(" FAIL: expected ratio >= 3.0 at R=" + R); + allPass = false; + } + } + + // Correctness: fixed produces same dependencies as defective + { + int R = 20; + List rules = new ArrayList<>(R); + for (int i = 0; i < R; i++) rules.add(new Rule(i)); + Map> fwd = buildForwardMap(rules, 2); + Map> inv = buildInverseMap(fwd); + + boolean correct = true; + for (Rule r : rules) { + // Collect via defective + List defDeps = new ArrayList<>(); + for (Map.Entry> e : fwd.entrySet()) { + if (e.getValue().contains(r)) defDeps.add(e.getKey()); + } + // Collect via fixed + List fixDeps = inv.getOrDefault(r, Collections.emptyList()); + + Set defIds = new HashSet<>(); + for (Rule d : defDeps) defIds.add(d.id); + Set fixIds = new HashSet<>(); + for (Rule d : fixDeps) fixIds.add(d.id); + + if (!defIds.equals(fixIds)) { + System.err.println(" FAIL: mismatch for " + r + " defective=" + defIds + " fixed=" + fixIds); + correct = false; + allPass = false; + } + } + if (correct) { + System.out.println("Correctness check: PASS (dependency sets match)"); + } + } + + if (allPass) { + System.out.println("ALL TESTS PASS"); + System.exit(0); + } else { + System.out.println("SOME TESTS FAILED"); + System.exit(1); + } } }