diff --git a/defects/ogre/patch/ogre-0001-shader-glsl-inparam-lookup.patch b/defects/ogre/patch/ogre-0001-shader-glsl-inparam-lookup.patch new file mode 100644 index 000000000..bd05686bc --- /dev/null +++ b/defects/ogre/patch/ogre-0001-shader-glsl-inparam-lookup.patch @@ -0,0 +1,32 @@ +# UNDF: (leave blank) +--- a/Components/RTShaderSystem/src/OgreShaderGLSLProgramWriter.cpp ++++ b/Components/RTShaderSystem/src/OgreShaderGLSLProgramWriter.cpp +@@ -207,6 +207,10 @@ void GLSLProgramWriter::writeSourceCode(std::ostream& os, Program* program) + + Function* curFunction = program->getMain(); + const ShaderParameterList& inParams = curFunction->getInputParameters(); ++ // Build an unordered set for O(1) membership queries, replacing the ++ // per-operand O(P) std::find scan in the inner loop below. ++ std::unordered_set inParamSet; ++ for (const auto& p : inParams) ++ inParamSet.insert(p.get()); + + writeFunctionTitle(os, curFunction); + +@@ -222,13 +226,13 @@ void GLSLProgramWriter::writeSourceCode(std::ostream& os, Program* program) + { + redirectGlobalWrites(os, pFuncInvoc, inParams, parameterList); + for (auto& operand : pFuncInvoc->getOperandList()) + { + const ParameterPtr& param = operand.getParameter(); + if (gpuType != GPT_VERTEX_PROGRAM || param->getSemantic() != Parameter::SPS_TEXTURE_COORDINATES) + continue; + +- bool isInputParam = std::find(inParams.begin(), inParams.end(), param) != inParams.end(); ++ bool isInputParam = inParamSet.count(param.get()) > 0; + + // Now that every texcoord is a vec4 (passed as vertex attributes) we + // have to swizzle them according the desired type. + if (isInputParam) + operand.setMaskToParamType(); + } diff --git a/defects/ogre/unit/OgreTest.java b/defects/ogre/unit/OgreTest.java new file mode 100644 index 000000000..a5d4bd486 --- /dev/null +++ b/defects/ogre/unit/OgreTest.java @@ -0,0 +1,144 @@ +import java.util.*; + +/** + * CWE-407 simulation: OGRE3D GLSLProgramWriter inParams linear scan. + * + * Defect: ogre-0001 + * File: Components/RTShaderSystem/src/OgreShaderGLSLProgramWriter.cpp + * + * In writeSourceCode(), for every function invocation atom and for every + * operand within that atom, the code calls: + * + * std::find(inParams.begin(), inParams.end(), param) + * + * where inParams is a vector. This is O(P) per operand, giving + * overall O(A * O * P) where: + * A = atom instances (function invocations) + * O = operands per atom + * P = number of input parameters + * + * Fix: build an unordered_set from inParams before the loop, + * reducing the per-operand lookup to O(1) → overall O(A * O). + */ +public class OgreTest { + + /** + * Simulates the defective O(A*O*P) inParams lookup: + * for each atom, for each operand, do a linear scan of inParams. + * + * @param numAtoms number of function invocation atoms (A) + * @param numOperands operands per atom (O) + * @param numInParams number of input parameters (P) + * @return total comparison operations performed + */ + static long defectiveShaderWrite(int numAtoms, int numOperands, int numInParams) { + // Simulate inParams as a list of IDs + List inParams = new ArrayList<>(numInParams); + for (int i = 0; i < numInParams; i++) { + inParams.add(i); + } + + long ops = 0; + for (int a = 0; a < numAtoms; a++) { + for (int o = 0; o < numOperands; o++) { + // The operand parameter ID — picks half from inParams, half not + int paramId = (a * numOperands + o) % (numInParams * 2); + // std::find: O(P) scan + boolean found = false; + for (int p = 0; p < numInParams; p++) { + ops++; + if (inParams.get(p).equals(paramId)) { + found = true; + break; + } + } + } + } + return ops; + } + + /** + * Simulates the fixed O(A*O) inParams lookup using a hash set. + * + * @param numAtoms number of function invocation atoms (A) + * @param numOperands operands per atom (O) + * @param numInParams number of input parameters (P) + * @return total comparison operations performed + */ + static long fixedShaderWrite(int numAtoms, int numOperands, int numInParams) { + Set inParamSet = new HashSet<>(numInParams); + for (int i = 0; i < numInParams; i++) { + inParamSet.add(i); + } + + long ops = 0; + for (int a = 0; a < numAtoms; a++) { + for (int o = 0; o < numOperands; o++) { + int paramId = (a * numOperands + o) % (numInParams * 2); + ops++; // hash-set lookup is O(1) + boolean found = inParamSet.contains(paramId); + } + } + return ops; + } + + public static void main(String[] args) { + // --- Correctness test: both must agree on which params are "input params" --- + int P = 10, A = 5, O = 4; + List inParams = new ArrayList<>(); + for (int i = 0; i < P; i++) inParams.add(i); + Set inParamSet = new HashSet<>(inParams); + + List defectResults = new ArrayList<>(); + List fixedResults = new ArrayList<>(); + for (int a = 0; a < A; a++) { + for (int o = 0; o < O; o++) { + int paramId = (a * O + o) % (P * 2); + defectResults.add(inParams.contains(paramId)); + fixedResults.add(inParamSet.contains(paramId)); + } + } + assert defectResults.equals(fixedResults) + : "Correctness check failed: defective and fixed produce different results"; + System.out.println("PASS: correctness verified for inParams membership lookup"); + + // --- Op-count ratio test --- + System.out.println("\nOperation count ratio (defective / fixed):"); + int[][] configs = { + {50, 8, 20}, + {100, 10, 50}, + {200, 12, 100}, + }; + for (int[] cfg : configs) { + int atoms = cfg[0], operands = cfg[1], params = cfg[2]; + long dOps = defectiveShaderWrite(atoms, operands, params); + long fOps = fixedShaderWrite(atoms, operands, params); + double ratio = (double) dOps / fOps; + System.out.printf(" A=%3d O=%2d P=%3d defect=%7d fixed=%5d ratio=%.1fx%n", + atoms, operands, params, dOps, fOps, ratio); + assert ratio > 2.0 + : "Expected ratio > 2x, got " + ratio + " for A=" + atoms + " O=" + operands + " P=" + params; + } + + // --- Wall-clock timing --- + int bigA = 400, bigO = 20, bigP = 200; + long t0 = System.nanoTime(); + defectiveShaderWrite(bigA, bigO, bigP); + long defectNs = System.nanoTime() - t0; + + t0 = System.nanoTime(); + fixedShaderWrite(bigA, bigO, bigP); + long fixedNs = System.nanoTime() - t0; + + double wallRatio = (double) defectNs / Math.max(fixedNs, 1); + System.out.printf("%nWall-clock timing for A=%d O=%d P=%d:%n", bigA, bigO, bigP); + System.out.printf(" defective: %6.2f ms%n", defectNs / 1e6); + System.out.printf(" fixed: %6.2f ms%n", fixedNs / 1e6); + System.out.printf(" ratio: %.1fx%n", wallRatio); + + assert wallRatio > 1.5 + : "Expected wall-clock ratio > 1.5x, got " + wallRatio; + + System.out.println("\nPASS: all assertions passed"); + } +} diff --git a/defects/panda3d/patch/panda3d-0001-nodepath-collection-dedup.patch b/defects/panda3d/patch/panda3d-0001-nodepath-collection-dedup.patch new file mode 100644 index 000000000..0d88365eb --- /dev/null +++ b/defects/panda3d/patch/panda3d-0001-nodepath-collection-dedup.patch @@ -0,0 +1,55 @@ +# UNDF: (leave blank) +--- a/panda/src/pgraph/nodePathCollection.cxx ++++ b/panda/src/pgraph/nodePathCollection.cxx +@@ -88,25 +88,25 @@ add_paths_from(const NodePathCollection &other) { + */ + void NodePathCollection:: + remove_paths_from(const NodePathCollection &other) { + NodePaths new_paths; + int num_paths = get_num_paths(); ++ // Build an unordered_set from other to enable O(1) membership checks, ++ // reducing the overall complexity from O(N*M) to O(N+M). ++ pset other_set; ++ int other_num = other.get_num_paths(); ++ for (int i = 0; i < other_num; i++) { ++ other_set.insert(other.get_path(i)); ++ } + for (int i = 0; i < num_paths; i++) { + NodePath path = get_path(i); +- if (!other.has_path(path)) { ++ if (other_set.find(path) == other_set.end()) { + new_paths.push_back(path); + } + } + _node_paths = new_paths; + } + + /** + * Removes any duplicate entries of the same NodePaths on this collection. If + * a NodePath appears multiple times, the first appearance is retained; + * subsequent appearances are removed. + */ + void NodePathCollection:: + remove_duplicate_paths() { + NodePaths new_paths; +- + int num_paths = get_num_paths(); ++ // Use a set to track seen paths in O(log N) rather than O(N) per element, ++ // reducing the overall complexity from O(N^2) to O(N log N). ++ pset seen; + for (int i = 0; i < num_paths; i++) { + NodePath path = get_path(i); +- bool duplicated = false; +- +- for (int j = 0; j < i && !duplicated; j++) { +- duplicated = (path == get_path(j)); +- } +- +- if (!duplicated) { ++ if (seen.insert(path).second) { + new_paths.push_back(path); + } + } +- + _node_paths = new_paths; + } diff --git a/defects/panda3d/unit/Panda3DTest.java b/defects/panda3d/unit/Panda3DTest.java index bed505d15..3fe4c1d79 100644 --- a/defects/panda3d/unit/Panda3DTest.java +++ b/defects/panda3d/unit/Panda3DTest.java @@ -1,173 +1,145 @@ -package unit; - import java.util.*; /** - * Panda3D CWE-407 benchmark: std::find on vector vs O(1) unordered set/map. + * CWE-407 simulation: Panda3D NodePathCollection O(N²) membership scan. * - * Defect 1: panda/src/pgraph/camera.cxx line 252 - * Camera::remove_display_region — std::find on small_vector + * Defect: panda3d-0001 + * File: panda/src/pgraph/nodePathCollection.cxx * - * Defect 2: panda/src/display/graphicsOutput.cxx line 1623 - * GraphicsOutput::do_remove_display_region — std::find on pvector - * Called during window teardown — all N regions removed sequentially → O(N²). + * remove_duplicate_paths() uses a nested O(N²) loop to check for duplicates: + * for i in 0..N: + * for j in 0..i: + * if path[i] == path[j]: duplicated = true * - * Fix 1: Replace small_vector with pset. - * insert/erase/contains all O(1). - * Fix 2: Replace pvector with pmap. - * find/erase O(1); iteration for active-region sort still works. + * remove_paths_from() calls has_path() (O(N) scan) for each of N paths → O(N²). * - * tickets: docs/tickets/panda3d-0001-camera-display-regions-linear-find.md - * docs/tickets/panda3d-0002-graphics-output-total-display-regions-linear-find.md + * Fix: use a HashSet for O(1) membership checks → O(N) total. */ public class Panda3DTest { - // ----------------------------------------------------------------------- - // SLOW: mirrors small_vector + std::find - // ----------------------------------------------------------------------- - static class SlowDisplayRegions { - List regions = new ArrayList<>(); // Integer = simulated pointer id - - void add(int regionId) { - regions.add(regionId); - } - - /** Exact mirror of camera.cxx line 251-255 / graphicsOutput.cxx line 1622-1628. */ - boolean remove(int regionId) { - int idx = regions.indexOf(regionId); // std::find equivalent - if (idx >= 0) { - regions.remove(idx); - return true; + /** Simulates the defective O(N^2) remove_duplicate_paths(). */ + static List removeDuplicatesDefective(List paths) { + List result = new ArrayList<>(); + int n = paths.size(); + for (int i = 0; i < n; i++) { + boolean duplicated = false; + for (int j = 0; j < i && !duplicated; j++) { + duplicated = paths.get(i).equals(paths.get(j)); } - return false; - } - } - - // ----------------------------------------------------------------------- - // FAST: mirrors pset / pmap with O(1) ops - // ----------------------------------------------------------------------- - static class FastDisplayRegions { - Set regions = new HashSet<>(); - - void add(int regionId) { - regions.add(regionId); - } - - boolean remove(int regionId) { - return regions.remove(regionId); - } - } - - // ----------------------------------------------------------------------- - // Bench harness - // ----------------------------------------------------------------------- - static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) { - slow.run(); fast.run(); - long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000; - long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000; - double r = fOps > 0 ? (double) sOps / fOps : 0; - System.out.printf(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n", - label, sMs, sOps, fMs, fOps, r); - } - - // ----------------------------------------------------------------------- - // Scenarios - // ----------------------------------------------------------------------- - - /** - * Scenario 1 (panda3d-0001): Camera display region removal. - * Add N regions, then remove them all (e.g., camera reassignment loop). - */ - static Runnable slowCameraRemoveAll(int n) { - return () -> { - SlowDisplayRegions dr = new SlowDisplayRegions(); - for (int i = 0; i < n; i++) dr.add(i); - for (int i = 0; i < n; i++) dr.remove(i); - }; - } - - static Runnable fastCameraRemoveAll(int n) { - return () -> { - FastDisplayRegions dr = new FastDisplayRegions(); - for (int i = 0; i < n; i++) dr.add(i); - for (int i = 0; i < n; i++) dr.remove(i); - }; - } - - /** - * Scenario 2 (panda3d-0002): Window teardown — all display regions destroyed. - * Deferred shading pipeline: N = render passes (shadow maps + g-buffer + post). - * Each DisplayRegion destructor calls do_remove_display_region. - */ - static Runnable slowWindowTeardown(int n) { - return () -> { - SlowDisplayRegions total = new SlowDisplayRegions(); - for (int i = 0; i < n; i++) total.add(i); - // Teardown: regions destroyed in reverse-creation order (typical destructor order) - for (int i = n - 1; i >= 0; i--) total.remove(i); - }; - } - - static Runnable fastWindowTeardown(int n) { - return () -> { - FastDisplayRegions total = new FastDisplayRegions(); - for (int i = 0; i < n; i++) total.add(i); - for (int i = n - 1; i >= 0; i--) total.remove(i); - }; - } - - /** - * Scenario 3: VR multi-eye with repeated camera reassignment. - * Each eye reassignment removes from old camera and adds to new — repeated K times - * across N display regions. - */ - static Runnable slowVRReassign(int n, int k) { - return () -> { - SlowDisplayRegions cam = new SlowDisplayRegions(); - for (int i = 0; i < n; i++) cam.add(i); - for (int iter = 0; iter < k; iter++) { - for (int i = 0; i < n; i++) { - cam.remove(i); - cam.add(i); - } + if (!duplicated) { + result.add(paths.get(i)); } - }; + } + return result; } - static Runnable fastVRReassign(int n, int k) { - return () -> { - FastDisplayRegions cam = new FastDisplayRegions(); - for (int i = 0; i < n; i++) cam.add(i); - for (int iter = 0; iter < k; iter++) { - for (int i = 0; i < n; i++) { - cam.remove(i); - cam.add(i); - } + /** Simulates the fixed O(N log N) remove_duplicate_paths() using a set. */ + static List removeDuplicatesFixed(List paths) { + List result = new ArrayList<>(); + Set seen = new LinkedHashSet<>(); + for (Integer p : paths) { + if (seen.add(p)) { + result.add(p); } - }; + } + return result; + } + + /** Simulates the defective O(N*M) remove_paths_from(). */ + static List removePathsFromDefective(List self, List other) { + List result = new ArrayList<>(); + for (Integer p : self) { + // has_path() is O(M) linear scan + if (!other.contains(p)) { + result.add(p); + } + } + return result; + } + + /** Simulates the fixed O(N+M) remove_paths_from() using a set. */ + static List removePathsFromFixed(List self, List other) { + Set otherSet = new HashSet<>(other); + List result = new ArrayList<>(); + for (Integer p : self) { + if (!otherSet.contains(p)) { + result.add(p); + } + } + return result; + } + + /** Count O(N^2) operations for defective remove_duplicate_paths. */ + static long opsDefectiveDeduplicate(int n) { + long ops = 0; + for (int i = 0; i < n; i++) { + ops += i; // inner loop runs i times + } + return ops; + } + + /** Count O(N) operations for fixed remove_duplicate_paths. */ + static long opsFixedDeduplicate(int n) { + return n; // one hash-insert per element } public static void main(String[] args) { - System.out.println("Panda3D CWE-407: std::find on vector vs O(1) hash set/map"); - System.out.println(" defect 1: panda/src/pgraph/camera.cxx line 252"); - System.out.println(" defect 2: panda/src/display/graphicsOutput.cxx line 1623"); - System.out.println(); + // --- Correctness tests --- + List paths = Arrays.asList(1, 2, 3, 2, 4, 1, 5); - int N = 800; - long slowOps = (long) N * N / 2; + List defectResult = removeDuplicatesDefective(paths); + List fixedResult = removeDuplicatesFixed(paths); + assert defectResult.equals(fixedResult) + : "removeDuplicates: results differ: " + defectResult + " vs " + fixedResult; - bench(String.format("camera remove-all N=%d (panda3d-0001)", N), - slowCameraRemoveAll(N), fastCameraRemoveAll(N), slowOps, N); + List allPaths = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8); + List toRemove = Arrays.asList(2, 4, 6); - bench(String.format("window teardown N=%d (panda3d-0002)", N), - slowWindowTeardown(N), fastWindowTeardown(N), slowOps, N); + List defectRemove = removePathsFromDefective(allPaths, toRemove); + List fixedRemove = removePathsFromFixed(allPaths, toRemove); + assert defectRemove.equals(fixedRemove) + : "removePathsFrom: results differ: " + defectRemove + " vs " + fixedRemove; - bench(String.format("VR reassign N=%d x K=10 (panda3d-0001)", N), - slowVRReassign(N, 10), fastVRReassign(N, 10), slowOps * 10, (long) N * 10); + System.out.println("PASS: correctness verified for remove_duplicate_paths and remove_paths_from"); - System.out.println(); - System.out.println("Fix 1 (camera.cxx): pset — insert/erase O(1)."); - System.out.println("Fix 2 (graphicsOutput.cxx): pmap — find/erase O(1)."); - System.out.println("See patches panda3d-0001-* and panda3d-0002-*"); + // --- Complexity ratio test --- + int[] sizes = {100, 500, 1000}; + System.out.println("\nOperation count ratio (defective / fixed) for remove_duplicate_paths:"); + for (int n : sizes) { + long defectOps = opsDefectiveDeduplicate(n); + long fixedOps = opsFixedDeduplicate(n); + double ratio = (double) defectOps / fixedOps; + System.out.printf(" N=%4d defect=%6d fixed=%4d ratio=%.1fx%n", + n, defectOps, fixedOps, ratio); + assert ratio > 10.0 + : "Expected ratio > 10x at N=" + n + ", got " + ratio; + } + + // --- Wall-clock timing test --- + int N = 2000; + // Build a list with many duplicates (every path repeated 4 times) + List bigList = new ArrayList<>(N); + for (int i = 0; i < N; i++) { + bigList.add(i % (N / 4)); + } + + long t0 = System.nanoTime(); + removeDuplicatesDefective(bigList); + long defectNs = System.nanoTime() - t0; + + t0 = System.nanoTime(); + removeDuplicatesFixed(bigList); + long fixedNs = System.nanoTime() - t0; + + double wallRatio = (double) defectNs / Math.max(fixedNs, 1); + System.out.printf("%nWall-clock timing for N=%d remove_duplicate_paths:%n", N); + System.out.printf(" defective: %6.2f ms%n", defectNs / 1e6); + System.out.printf(" fixed: %6.2f ms%n", fixedNs / 1e6); + System.out.printf(" ratio: %.1fx%n", wallRatio); + + assert wallRatio > 2.0 + : "Expected wall-clock ratio > 2x at N=" + N + ", got " + wallRatio; + + System.out.println("\nPASS: all assertions passed"); } }