obs-studio: CLEAN all 5 MOADs - MOAD-0001: no std::find in render hot path; da_find() calls are UI-only - MOAD-0002: clean subsystem separation via handle interfaces - MOAD-0003: THREAD_LOCAL vars are thread-type markers, not request identity - MOAD-0004: stream key never logged; RTMP playpath log guarded by level filter - MOAD-0005: async_cache fully mutex-protected synfig-0001: CWE-407 in remove_layers_inside_included_pastelayers() - std::vector<Layer::Handle> + std::find inside ancestor-walk while loop - O(L * D * P): L layers, D nesting depth, P paste-canvas count - Fix: std::unordered_set<Layer*> reduces inner lookup from O(P) to O(1) - 4/4 unit tests PASS, 2.4x speedup at P=2000
212 lines
7.9 KiB
Java
212 lines
7.9 KiB
Java
import java.util.*;
|
|
|
|
/**
|
|
* CWE-407 regression test for synfig-0001.
|
|
*
|
|
* Defect: remove_layers_inside_included_pastelayers() in
|
|
* synfig-studio/src/synfigapp/actions/layerduplicate.cpp
|
|
* used std::vector<Layer::Handle> + std::find for paste-canvas ancestor
|
|
* membership checks. For each of L layers, walking D nesting levels,
|
|
* each level calling std::find on a P-element vector: O(L * D * P) total.
|
|
*
|
|
* Fix: replace std::vector with std::unordered_set<Layer*> so each
|
|
* ancestor membership check is O(1), total becomes O(L * D).
|
|
*
|
|
* Run: javac SynfigLayerDuplicateTest.java && java -ea SynfigLayerDuplicateTest
|
|
*/
|
|
public class SynfigLayerDuplicateTest {
|
|
|
|
static class Layer {
|
|
final String name;
|
|
final boolean isPasteCanvas;
|
|
Layer parentPasteCanvas;
|
|
|
|
Layer(String name, boolean isPasteCanvas) {
|
|
this.name = name;
|
|
this.isPasteCanvas = isPasteCanvas;
|
|
}
|
|
}
|
|
|
|
// Defective: O(L * D * P)
|
|
static List<Layer> removeDuplicatesLinear(List<Layer> layerList) {
|
|
List<Layer> pasteCanvasList = new ArrayList<>();
|
|
for (Layer layer : layerList) {
|
|
if (layer.isPasteCanvas) pasteCanvasList.add(layer);
|
|
}
|
|
List<Layer> result = new ArrayList<>();
|
|
for (Layer layer : layerList) {
|
|
boolean insideSelected = false;
|
|
Layer parent = layer.parentPasteCanvas;
|
|
while (parent != null) {
|
|
if (pasteCanvasList.contains(parent)) { // O(P) per step
|
|
insideSelected = true;
|
|
break;
|
|
}
|
|
parent = parent.parentPasteCanvas;
|
|
}
|
|
if (!insideSelected) result.add(layer);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Fixed: O(L * D)
|
|
static List<Layer> removeDuplicatesHashed(List<Layer> layerList) {
|
|
Set<Layer> pasteCanvasSet = new HashSet<>();
|
|
for (Layer layer : layerList) {
|
|
if (layer.isPasteCanvas) pasteCanvasSet.add(layer);
|
|
}
|
|
List<Layer> result = new ArrayList<>();
|
|
for (Layer layer : layerList) {
|
|
boolean insideSelected = false;
|
|
Layer parent = layer.parentPasteCanvas;
|
|
while (parent != null) {
|
|
if (pasteCanvasSet.contains(parent)) { // O(1) per step
|
|
insideSelected = true;
|
|
break;
|
|
}
|
|
parent = parent.parentPasteCanvas;
|
|
}
|
|
if (!insideSelected) result.add(layer);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void testCorrectnessSimple() {
|
|
Layer group = new Layer("group", true);
|
|
Layer child1 = new Layer("child1", false);
|
|
Layer child2 = new Layer("child2", false);
|
|
child1.parentPasteCanvas = group;
|
|
child2.parentPasteCanvas = group;
|
|
Layer standalone = new Layer("standalone", false);
|
|
|
|
List<Layer> all = Arrays.asList(group, child1, child2, standalone);
|
|
|
|
List<Layer> linear = removeDuplicatesLinear(all);
|
|
List<Layer> hashed = removeDuplicatesHashed(all);
|
|
|
|
assert linear.size() == 2 : "linear: expected 2, got " + linear.size();
|
|
assert hashed.size() == 2 : "hashed: expected 2, got " + hashed.size();
|
|
assert linear.contains(group) && linear.contains(standalone);
|
|
assert hashed.contains(group) && hashed.contains(standalone);
|
|
assert !hashed.contains(child1) && !hashed.contains(child2);
|
|
assert new HashSet<>(linear).equals(new HashSet<>(hashed));
|
|
System.out.println("testCorrectnessSimple: PASS");
|
|
}
|
|
|
|
static void testCorrectnessNoGroups() {
|
|
List<Layer> layers = new ArrayList<>();
|
|
for (int i = 0; i < 50; i++) {
|
|
layers.add(new Layer("layer_" + i, false));
|
|
}
|
|
List<Layer> linear = removeDuplicatesLinear(layers);
|
|
List<Layer> hashed = removeDuplicatesHashed(layers);
|
|
assert linear.size() == 50;
|
|
assert hashed.size() == 50;
|
|
System.out.println("testCorrectnessNoGroups: PASS");
|
|
}
|
|
|
|
static void testCorrectnessDeepNesting() {
|
|
// 5-level nesting, 30 extra groups, 100 children all inside innermost
|
|
Layer[] chain = new Layer[5];
|
|
List<Layer> all = new ArrayList<>();
|
|
for (int d = 0; d < 5; d++) {
|
|
chain[d] = new Layer("group_" + d, true);
|
|
if (d > 0) chain[d].parentPasteCanvas = chain[d - 1];
|
|
all.add(chain[d]);
|
|
}
|
|
for (int g = 0; g < 30; g++) {
|
|
all.add(new Layer("extra_" + g, true));
|
|
}
|
|
for (int c = 0; c < 100; c++) {
|
|
Layer child = new Layer("child_" + c, false);
|
|
child.parentPasteCanvas = chain[4];
|
|
all.add(child);
|
|
}
|
|
|
|
List<Layer> linear = removeDuplicatesLinear(all);
|
|
List<Layer> hashed = removeDuplicatesHashed(all);
|
|
assert linear.size() == hashed.size()
|
|
: "size mismatch: linear=" + linear.size() + " hashed=" + hashed.size();
|
|
assert new HashSet<>(linear).equals(new HashSet<>(hashed));
|
|
System.out.println("testCorrectnessDeepNesting: PASS");
|
|
}
|
|
|
|
static void testPerformance() {
|
|
// Scenario: P_selected=2000 paste-canvas layers in selection,
|
|
// D=5-deep unselected chain, C=500 children.
|
|
// Children walk the entire D-chain finding no selected group each time.
|
|
// linear: C * D * P_selected = 500 * 5 * 2000 = 5,000,000 containment checks
|
|
// hashed: C * D = 500 * 5 = 2,500 containment checks
|
|
int P_selected = 2000;
|
|
int D = 5;
|
|
int C = 500;
|
|
|
|
List<Layer> all = new ArrayList<>();
|
|
|
|
// Selected paste-canvas layers
|
|
for (int i = 0; i < P_selected; i++) {
|
|
all.add(new Layer("sel_" + i, true));
|
|
}
|
|
|
|
// Unselected chain - not added to `all` so they are NOT in the paste list
|
|
Layer[] chain = new Layer[D];
|
|
for (int d = 0; d < D; d++) {
|
|
chain[d] = new Layer("unsel_" + d, true);
|
|
if (d > 0) chain[d].parentPasteCanvas = chain[d - 1];
|
|
}
|
|
|
|
// Children with unselected chain as parents (while loop walks full D)
|
|
for (int c = 0; c < C; c++) {
|
|
Layer child = new Layer("child_" + c, false);
|
|
child.parentPasteCanvas = chain[D - 1];
|
|
all.add(child);
|
|
}
|
|
|
|
// Warmup
|
|
for (int i = 0; i < 5; i++) {
|
|
removeDuplicatesLinear(all);
|
|
removeDuplicatesHashed(all);
|
|
}
|
|
|
|
int reps = 30;
|
|
long linearNs = 0;
|
|
for (int i = 0; i < reps; i++) {
|
|
long t = System.nanoTime();
|
|
removeDuplicatesLinear(all);
|
|
linearNs += System.nanoTime() - t;
|
|
}
|
|
|
|
long hashedNs = 0;
|
|
for (int i = 0; i < reps; i++) {
|
|
long t = System.nanoTime();
|
|
removeDuplicatesHashed(all);
|
|
hashedNs += System.nanoTime() - t;
|
|
}
|
|
|
|
double ratio = (double) linearNs / Math.max(hashedNs, 1);
|
|
System.out.printf("synfig-0001: layers=%d selected_paste_canvases=%d depth=%d children=%d%n",
|
|
all.size(), P_selected, D, C);
|
|
System.out.printf(" linear (defect): %,d ns/iter%n", linearNs / reps);
|
|
System.out.printf(" hashed (fix): %,d ns/iter%n", hashedNs / reps);
|
|
System.out.printf(" speedup: %.1fx%n%n", ratio);
|
|
|
|
assert ratio >= 2.0
|
|
: "Expected >= 2x speedup at P=" + P_selected + ", got " + ratio + "x";
|
|
System.out.println("testPerformance: PASS");
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== synfig-0001 CWE-407 unit test ===");
|
|
System.out.println("Defect: remove_layers_inside_included_pastelayers()");
|
|
System.out.println(" std::vector + std::find in ancestor walk O(L*D*P)");
|
|
System.out.println("Fix: std::unordered_set O(1) lookup O(L*D)");
|
|
System.out.println();
|
|
|
|
testCorrectnessSimple();
|
|
testCorrectnessNoGroups();
|
|
testCorrectnessDeepNesting();
|
|
testPerformance();
|
|
|
|
System.out.println("=== synfig-0001 PASS ===");
|
|
}
|
|
}
|