java-topology/defects/panda3d/unit/Panda3DTest.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

145 lines
5.3 KiB
Java

import java.util.*;
/**
* CWE-407 simulation: Panda3D NodePathCollection O(N²) membership scan.
*
* Defect: panda3d-0001
* File: panda/src/pgraph/nodePathCollection.cxx
*
* 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
*
* remove_paths_from() calls has_path() (O(N) scan) for each of N paths → O(N²).
*
* Fix: use a HashSet for O(1) membership checks → O(N) total.
*/
public class Panda3DTest {
/** Simulates the defective O(N^2) remove_duplicate_paths(). */
static List<Integer> removeDuplicatesDefective(List<Integer> paths) {
List<Integer> 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));
}
if (!duplicated) {
result.add(paths.get(i));
}
}
return result;
}
/** Simulates the fixed O(N log N) remove_duplicate_paths() using a set. */
static List<Integer> removeDuplicatesFixed(List<Integer> paths) {
List<Integer> result = new ArrayList<>();
Set<Integer> 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<Integer> removePathsFromDefective(List<Integer> self, List<Integer> other) {
List<Integer> 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<Integer> removePathsFromFixed(List<Integer> self, List<Integer> other) {
Set<Integer> otherSet = new HashSet<>(other);
List<Integer> 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) {
// --- Correctness tests ---
List<Integer> paths = Arrays.asList(1, 2, 3, 2, 4, 1, 5);
List<Integer> defectResult = removeDuplicatesDefective(paths);
List<Integer> fixedResult = removeDuplicatesFixed(paths);
assert defectResult.equals(fixedResult)
: "removeDuplicates: results differ: " + defectResult + " vs " + fixedResult;
List<Integer> allPaths = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
List<Integer> toRemove = Arrays.asList(2, 4, 6);
List<Integer> defectRemove = removePathsFromDefective(allPaths, toRemove);
List<Integer> fixedRemove = removePathsFromFixed(allPaths, toRemove);
assert defectRemove.equals(fixedRemove)
: "removePathsFrom: results differ: " + defectRemove + " vs " + fixedRemove;
System.out.println("PASS: correctness verified for remove_duplicate_paths and remove_paths_from");
// --- 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<Integer> 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");
}
}