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
145 lines
5.3 KiB
Java
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");
|
|
}
|
|
}
|