java-topology/defects/libopenshot-0001/patch/libopenshot-0001.patch
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

72 lines
3.3 KiB
Diff

# UNDF: (leave blank)
# CWE-407: Algorithmic Complexity (list membership inside per-frame loop)
# libopenshot ObjectDetection effect: display_classes filter uses std::find on a
# std::vector<std::string> inside the per-detection loop that runs every rendered frame.
#
# Defective path (src/effects/ObjectDetection.cpp, two sites):
#
# Site 1 (line 76-79, GetFrame hot path):
# for (int i = 0; i < detections.boxes.size(); i++) {
# if (!display_classes.empty() &&
# std::find(display_classes.begin(), display_classes.end(),
# classNames[detections.classIds.at(i)]) == display_classes.end())
# continue;
# }
#
# Site 2 (line 269-284, GetPropertiesJSON):
# for (int i = 0; i < detections.boxes.size(); i++) {
# auto it = std::find(display_classes.begin(), display_classes.end(), className);
# }
#
# Complexity: O(D * C) per frame, where D = detections per frame, C = |display_classes|.
# With D=50 detections and C=20 filter classes this is 1000 string comparisons per frame.
# At 30 fps over a 1-minute video: 1,800,000 comparisons vs 90,000 with a hash set (20x).
#
# Fix: replace display_classes std::vector<std::string> with
# std::unordered_set<std::string>. Membership test drops from O(C) to O(1).
# SetJsonValue still populates the set with the same string values.
#
# Severity: MEDIUM. Hot path (every rendered frame), constant-factor overhead per
# detection scales with both frame rate and filter list length.
--- a/src/effects/ObjectDetection.h
+++ b/src/effects/ObjectDetection.h
@@ -72,7 +72,7 @@ namespace openshot {
float confidence_threshold;
bool display_box;
bool display_label;
- std::vector<std::string> display_classes;
+ std::unordered_set<std::string> display_classes;
/// @brief Default constructor
ObjectDetection(std::string clipObDetectDataPath="");
@@ -1,6 +1,7 @@
#include <string>
+#include <unordered_set>
--- a/src/effects/ObjectDetection.cpp
+++ b/src/effects/ObjectDetection.cpp
@@ -76,7 +76,7 @@ std::shared_ptr<Frame> ObjectDetection::GetFrame(std::shared_ptr<Frame> frame,
for (int i = 0; i < detections.boxes.size(); i++) {
if (detections.confidences.at(i) < confidence_threshold ||
(!display_classes.empty() &&
- std::find(display_classes.begin(), display_classes.end(), classNames[detections.classIds.at(i)]) == display_classes.end())) {
+ display_classes.find(classNames[detections.classIds.at(i)]) == display_classes.end())) {
continue;
}
@@ -279,7 +279,7 @@ std::string ObjectDetection::GetPropertiesJSON(int64_t requested_frame) const {
if (!display_classes.empty()) {
- auto it = std::find(display_classes.begin(), display_classes.end(), className);
- if (it == display_classes.end()) {
+ if (display_classes.find(className) == display_classes.end()) {
continue;
}
@@ -388,7 +388,6 @@ void ObjectDetection::SetJsonValue(const Json::Value root) {
display_classes.clear();
if (!root["display_classes"].isNull()) {
for (const auto &s : root["display_classes"]) {
- display_classes.push_back(s.toStyledString());
+ display_classes.insert(s.toStyledString());
}
}