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 + 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 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 removeDuplicatesLinear(List layerList) { List pasteCanvasList = new ArrayList<>(); for (Layer layer : layerList) { if (layer.isPasteCanvas) pasteCanvasList.add(layer); } List 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 removeDuplicatesHashed(List layerList) { Set pasteCanvasSet = new HashSet<>(); for (Layer layer : layerList) { if (layer.isPasteCanvas) pasteCanvasSet.add(layer); } List 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 all = Arrays.asList(group, child1, child2, standalone); List linear = removeDuplicatesLinear(all); List 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 layers = new ArrayList<>(); for (int i = 0; i < 50; i++) { layers.add(new Layer("layer_" + i, false)); } List linear = removeDuplicatesLinear(layers); List 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 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 linear = removeDuplicatesLinear(all); List 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 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 ==="); } }