java-topology/defects/openscad/test/OpenScadAmfVertexDedupTest.java
russell@unturf.com 3aaecab0e1 openscad-0001: AMF export vertex dedup std::find O(V²) → HashMap O(V)
Defect: export_amf.cc add_vertex() uses std::find on std::vector for every
vertex during AMF export. For meshes with V vertices, total cost is O(V²).

Fix: parallel unordered_map<vertex_str, size_t> for O(1) lookup.

Unit test: 5-16x ratio at V=500-5000, PASS.
2026-03-30 17:21:45 -04:00

106 lines
3.7 KiB
Java

import java.util.*;
/**
* Unit test for OpenSCAD MOAD-0001: export_amf.cc add_vertex() O(V²) vertex dedup.
*
* Defect: add_vertex() uses std::find on a vector to deduplicate vertices
* during AMF export. For a mesh with V unique vertices, each insertion scans
* the entire vector → O(V²) total.
*
* Fix: maintain a parallel HashMap for O(1) lookup, keeping the vector for
* index-ordered output.
*
* CWE-407: Inefficient Algorithmic Complexity
*/
public class OpenScadAmfVertexDedupTest {
// --- Defective: linear scan on list for every vertex ---
static int addVertexDefective(List<String> vertices, String v) {
int idx = vertices.indexOf(v);
if (idx == -1) {
vertices.add(v);
return vertices.size() - 1;
}
return idx;
}
// --- Fixed: HashMap for O(1) lookup ---
static int addVertexFixed(List<String> vertices, Map<String, Integer> index, String v) {
Integer idx = index.get(v);
if (idx == null) {
int newIdx = vertices.size();
vertices.add(v);
index.put(v, newIdx);
return newIdx;
}
return idx;
}
public static void main(String[] args) {
// Correctness test
testCorrectness();
// Performance test
testPerformance(500);
testPerformance(2000);
testPerformance(5000);
System.out.println("ALL TESTS PASSED");
}
static void testCorrectness() {
List<String> vertsDef = new ArrayList<>();
List<String> vertsFix = new ArrayList<>();
Map<String, Integer> index = new HashMap<>();
// Simulate vertices with some duplicates
String[] inputs = {
"1.0,2.0,3.0", "4.0,5.0,6.0", "1.0,2.0,3.0",
"7.0,8.0,9.0", "4.0,5.0,6.0", "10.0,11.0,12.0"
};
for (String v : inputs) {
int idxDef = addVertexDefective(vertsDef, v);
int idxFix = addVertexFixed(vertsFix, index, v);
assert idxDef == idxFix : "Index mismatch for " + v + ": " + idxDef + " vs " + idxFix;
}
assert vertsDef.size() == vertsFix.size() : "Size mismatch";
assert vertsDef.size() == 4 : "Expected 4 unique vertices, got " + vertsDef.size();
for (int i = 0; i < vertsDef.size(); i++) {
assert vertsDef.get(i).equals(vertsFix.get(i)) : "Content mismatch at " + i;
}
System.out.println("PASS correctness");
}
static void testPerformance(int V) {
// Generate V unique vertices, then re-insert them all (simulates mesh with shared vertices)
String[] verts = new String[V];
for (int i = 0; i < V; i++) {
verts[i] = i + ".0," + (i * 2) + ".0," + (i * 3) + ".0";
}
// Defective path: insert all unique, then look up all again
List<String> vertsDef = new ArrayList<>();
long t0 = System.nanoTime();
for (String v : verts) addVertexDefective(vertsDef, v);
for (String v : verts) addVertexDefective(vertsDef, v); // all hits = full scan each
long defectNs = System.nanoTime() - t0;
// Fixed path
List<String> vertsFix = new ArrayList<>();
Map<String, Integer> index = new HashMap<>();
long t1 = System.nanoTime();
for (String v : verts) addVertexFixed(vertsFix, index, v);
for (String v : verts) addVertexFixed(vertsFix, index, v);
long fixedNs = System.nanoTime() - t1;
double ratio = (double) defectNs / fixedNs;
System.out.printf("PASS V=%d defect=%dms fixed=%dms ratio=%.1fx%n",
V, defectNs / 1_000_000, fixedNs / 1_000_000, ratio);
assert ratio > 2.0 : "Expected ratio > 2x at V=" + V + " but got " + ratio;
}
}