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.
176 lines
6.7 KiB
Java
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);
|
|
}
|
|
}
|