import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Test for libopenshot-0001: CWE-407 display_classes filter in ObjectDetection * uses std::vector + std::find inside a per-frame detection loop. * * Pattern (src/effects/ObjectDetection.cpp, GetFrame + GetPropertiesJSON): * for (int i = 0; i < detections.boxes.size(); i++) { * std::find(display_classes.begin(), display_classes.end(), className) * } * * Complexity: O(D * C) per frame, D=detections, C=|display_classes|. * Fix: use std::unordered_set; membership drops to O(1). * * Compile and run (no build tool required): * javac defects/libopenshot-0001/unit/LibOpenshotObjectDetectionTest.java \ * -d /tmp/libopenshot-0001 * java -cp /tmp/libopenshot-0001 LibOpenshotObjectDetectionTest */ public class LibOpenshotObjectDetectionTest { private static int passed = 0; private static int failed = 0; // --- Defective: linear scan per detection --- static boolean isDisplayedDefective(List displayClasses, String className) { if (displayClasses.isEmpty()) return true; // O(C) linear scan — defect site for (String s : displayClasses) { if (s.equals(className)) return true; } return false; } static int renderFrameDefective(List displayClasses, List detections) { int rendered = 0; for (String cls : detections) { if (isDisplayedDefective(displayClasses, cls)) rendered++; } return rendered; } // --- Fixed: O(1) hash set membership --- static boolean isDisplayedFixed(Set displayClasses, String className) { if (displayClasses.isEmpty()) return true; return displayClasses.contains(className); // O(1) } static int renderFrameFixed(Set displayClasses, List detections) { int rendered = 0; for (String cls : detections) { if (isDisplayedFixed(displayClasses, cls)) rendered++; } return rendered; } // --- Helpers --- static List buildDetections(int count, String[] classPool) { List out = new ArrayList<>(count); for (int i = 0; i < count; i++) { out.add(classPool[i % classPool.length]); } return out; } static List buildFilterList(int size) { List out = new ArrayList<>(size); for (int i = 0; i < size; i++) out.add("class" + i); return out; } static Set buildFilterSet(int size) { Set out = new HashSet<>(size * 2); for (int i = 0; i < size; i++) out.add("class" + i); return out; } // --- Tests --- static void testEmptyFilterShowsAll() { List detections = List.of("person", "car", "dog"); int defectiveResult = renderFrameDefective(new ArrayList<>(), detections); int fixedResult = renderFrameFixed(new HashSet<>(), detections); check("empty filter: defective shows all detections", defectiveResult == 3); check("empty filter: fixed shows all detections", fixedResult == 3); } static void testFilterExcludes() { List filterList = List.of("person", "car"); Set filterSet = new HashSet<>(filterList); List detections = List.of("person", "car", "dog", "cat", "person"); int defectiveResult = renderFrameDefective(filterList, detections); int fixedResult = renderFrameFixed(filterSet, detections); check("filter excludes: defective result correct (3)", defectiveResult == 3); check("filter excludes: fixed result correct (3)", fixedResult == 3); } static void testFilterAllExcluded() { List filterList = List.of("bird"); Set filterSet = new HashSet<>(filterList); List detections = List.of("person", "car", "dog"); int defectiveResult = renderFrameDefective(filterList, detections); int fixedResult = renderFrameFixed(filterSet, detections); check("all excluded: defective shows 0", defectiveResult == 0); check("all excluded: fixed shows 0", fixedResult == 0); } static void testPerformanceRatio() { // D=50 detections, C=40 filter classes — simulate 30 fps for 60 seconds = 1800 frames String[] classPool = new String[10]; for (int i = 0; i < 10; i++) classPool[i] = "class" + i; List detections = buildDetections(50, classPool); List filterList = buildFilterList(40); Set filterSet = buildFilterSet(40); int frames = 1800; long t0 = System.nanoTime(); for (int f = 0; f < frames; f++) renderFrameDefective(filterList, detections); long defectiveNs = System.nanoTime() - t0; t0 = System.nanoTime(); for (int f = 0; f < frames; f++) renderFrameFixed(filterSet, detections); long fixedNs = System.nanoTime() - t0; double ratio = (double) defectiveNs / fixedNs; System.out.printf(" [perf] defective=%.1fms fixed=%.1fms ratio=%.1fx%n", defectiveNs / 1e6, fixedNs / 1e6, ratio); check("fixed path is at least 1.5x faster than defective (D=50, C=40, 1800 frames)", ratio >= 1.5); } static void testResultsMatchAcrossImplementations() { // Results must be identical regardless of implementation String[] classPool = {"person", "car", "dog", "cat", "bird", "truck"}; List detections = buildDetections(30, classPool); List filterList = List.of("person", "car", "truck"); Set filterSet = new HashSet<>(filterList); int defectiveResult = renderFrameDefective(filterList, detections); int fixedResult = renderFrameFixed(filterSet, detections); check("results match between defective and fixed implementations", defectiveResult == fixedResult); } // --- Harness --- static void check(String desc, boolean cond) { if (cond) { System.out.println(" PASS: " + desc); passed++; } else { System.out.println(" FAIL: " + desc); failed++; } } public static void main(String[] args) { System.out.println("=== LibOpenshotObjectDetectionTest (libopenshot-0001, CWE-407) ===\n"); testEmptyFilterShowsAll(); testFilterExcludes(); testFilterAllExcluded(); testResultsMatchAcrossImplementations(); testPerformanceRatio(); System.out.println("\n--- " + passed + " passed, " + failed + " failed ---"); if (failed > 0) System.exit(1); } }