java-topology/defects/synfig-0001/test/SynfigLayerDuplicateTest.java
russell@unturf.com a512770591 obs-studio + synfig: 5-MOAD scan; 1 defect, 1 CLEAN
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
2026-03-31 21:35:03 -04:00

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 ===");
}
}