java-topology/defects/ogre/unit/OgreTest.java
russell@unturf.com 75edb2a0a9 panda3d/ogre: CWE-407 findings
panda3d-0001: NodePathCollection remove_duplicate_paths O(N^2) nested scan
  and remove_paths_from has_path O(N*M) -> fix with pset, 499x at N=1000

ogre-0001: GLSLProgramWriter std::find(inParams) O(A*O*P) in nested loop
  during GLSL shader code generation -> fix with unordered_set, 75x at A=200
2026-03-30 09:27:50 -04:00

144 lines
5.5 KiB
Java

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<ParameterPtr>. 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<const Parameter*> 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<Integer> 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<Integer> 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<Integer> inParams = new ArrayList<>();
for (int i = 0; i < P; i++) inParams.add(i);
Set<Integer> inParamSet = new HashSet<>(inParams);
List<Boolean> defectResults = new ArrayList<>();
List<Boolean> 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");
}
}