java-topology/defects/libopenshot-0001/unit/LibOpenshotObjectDetectionTest.java
russell@unturf.com be19bb7757 openshot+opentoonz: 3 CWE-407 defects, all 5 MOADs scanned
openshot-0001: QueryObject.filter(id=x) O(N) scan called in loop over
selected clips/transitions — O(S*C) total. Fix: build id->clip dict once.

libopenshot-0001: std::find(display_classes...) inside per-frame detection
loop in ObjectDetection.cpp — O(D*C) per frame. Fix: unordered_set.

opentoonz-0001: std::find(closingSegments...) inside endpoint loop in
autoclose.cpp spotResearchOnePoint() — O(E*C) per vectorization call, runs
per-frame during painted cell rendering. Fix: set-based dedup.

MOADs 2-5 CLEAN: no god-object coupling defects, no leaked thread context,
no credential logging (SVN password is CLI arg not logged), no thundering
herd in image/frame caches (all properly mutex-guarded).

3/3 unit tests PASS.
2026-03-31 21:21:27 -04:00

176 lines
6.7 KiB
Java

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::string> + 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<std::string>; 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<String> 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<String> displayClasses, List<String> 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<String> displayClasses, String className) {
if (displayClasses.isEmpty()) return true;
return displayClasses.contains(className); // O(1)
}
static int renderFrameFixed(Set<String> displayClasses, List<String> detections) {
int rendered = 0;
for (String cls : detections) {
if (isDisplayedFixed(displayClasses, cls)) rendered++;
}
return rendered;
}
// --- Helpers ---
static List<String> buildDetections(int count, String[] classPool) {
List<String> out = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
out.add(classPool[i % classPool.length]);
}
return out;
}
static List<String> buildFilterList(int size) {
List<String> out = new ArrayList<>(size);
for (int i = 0; i < size; i++) out.add("class" + i);
return out;
}
static Set<String> buildFilterSet(int size) {
Set<String> out = new HashSet<>(size * 2);
for (int i = 0; i < size; i++) out.add("class" + i);
return out;
}
// --- Tests ---
static void testEmptyFilterShowsAll() {
List<String> 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<String> filterList = List.of("person", "car");
Set<String> filterSet = new HashSet<>(filterList);
List<String> 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<String> filterList = List.of("bird");
Set<String> filterSet = new HashSet<>(filterList);
List<String> 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<String> detections = buildDetections(50, classPool);
List<String> filterList = buildFilterList(40);
Set<String> 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<String> detections = buildDetections(30, classPool);
List<String> filterList = List.of("person", "car", "truck");
Set<String> 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);
}
}