openfoam-0002: CFCFaceToCellStencil::calcCellStencil allGlobalFaces dedup uses findIndex() O(G) linear scan inside nested forAll(cells)*forAll(faces) loop. Developer left comment "Note:should use hashset?" — confirmed defect. Fix: labelHashSet seen replaces findIndex, 77x op-count speedup at C=2000/F=12. stella: all 5 MOADs CLEAN. BreakpointMap uses unordered_map O(1); TrapArray uses std::array<uInt8,0x10000> O(1); single-threaded, no credentials, no thread-local identity, no thundering herd.
184 lines
6.9 KiB
Java
184 lines
6.9 KiB
Java
/**
|
|
* CWE-407 unit test for OpenFOAM CFCFaceToCellStencil.C calcCellStencil defect.
|
|
*
|
|
* Defect: calcCellStencil() builds per-cell stencils of neighbour face global
|
|
* indices using findIndex(allGlobalFaces, nbrGlobalI) — an O(G) linear scan —
|
|
* inside nested forAll loops over cells, cell faces, and neighbour faces.
|
|
* The developer even left the comment: "Note:should use hashset?"
|
|
*
|
|
* Fix: Replace findIndex membership check with a labelHashSet (here: HashSet<Integer>)
|
|
* giving O(1) dedup per insertion.
|
|
*
|
|
* Complexity:
|
|
* Defective: O(C * F * G) where G = accumulated global faces per cell, G ~ O(F^2)
|
|
* Fixed: O(C * F) with O(1) set insertion
|
|
*
|
|
* At C=1M cells, F=12 faces: ~12x theoretical speedup.
|
|
*/
|
|
import java.util.*;
|
|
|
|
public class OpenFoam0002Test {
|
|
|
|
/**
|
|
* Defective pattern: ArrayList dedup via contains() = O(G) per check.
|
|
* Returns operation count as proxy for work done.
|
|
*/
|
|
static long calcCellStencilDefective(int nCells, int facesPerCell) {
|
|
long ops = 0;
|
|
List<Integer> allGlobalFaces = new ArrayList<>();
|
|
|
|
for (int celli = 0; celli < nCells; celli++) {
|
|
allGlobalFaces.clear();
|
|
|
|
// My faces
|
|
for (int i = 0; i < facesPerCell; i++) {
|
|
int globalI = celli * facesPerCell + i;
|
|
allGlobalFaces.add(globalI);
|
|
ops++;
|
|
}
|
|
|
|
// Neighbour faces — each face shares a neighbour cell
|
|
for (int i = 0; i < facesPerCell; i++) {
|
|
int nbrCelli = (celli + i + 1) % nCells;
|
|
for (int j = 0; j < facesPerCell; j++) {
|
|
int nbrGlobalI = nbrCelli * facesPerCell + j;
|
|
ops += allGlobalFaces.size(); // O(G) linear scan
|
|
if (!allGlobalFaces.contains(nbrGlobalI)) {
|
|
allGlobalFaces.add(nbrGlobalI);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
/**
|
|
* Fixed pattern: HashSet for O(1) dedup, keep ordered list for output.
|
|
* Returns operation count.
|
|
*/
|
|
static long calcCellStencilFixed(int nCells, int facesPerCell) {
|
|
long ops = 0;
|
|
List<Integer> allGlobalFaces = new ArrayList<>();
|
|
Set<Integer> seen = new HashSet<>();
|
|
|
|
for (int celli = 0; celli < nCells; celli++) {
|
|
allGlobalFaces.clear();
|
|
seen.clear();
|
|
|
|
// My faces
|
|
for (int i = 0; i < facesPerCell; i++) {
|
|
int globalI = celli * facesPerCell + i;
|
|
ops++;
|
|
if (seen.add(globalI)) {
|
|
allGlobalFaces.add(globalI);
|
|
}
|
|
}
|
|
|
|
// Neighbour faces
|
|
for (int i = 0; i < facesPerCell; i++) {
|
|
int nbrCelli = (celli + i + 1) % nCells;
|
|
for (int j = 0; j < facesPerCell; j++) {
|
|
int nbrGlobalI = nbrCelli * facesPerCell + j;
|
|
ops++; // O(1) hash set check
|
|
if (seen.add(nbrGlobalI)) {
|
|
allGlobalFaces.add(nbrGlobalI);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
/**
|
|
* Verify both versions produce identical stencil sets for a small mesh.
|
|
*/
|
|
static boolean verifyCorrectness(int nCells, int facesPerCell) {
|
|
// Run both and collect results
|
|
List<Set<Integer>> defResult = new ArrayList<>();
|
|
List<Integer> allGlobalFacesDef = new ArrayList<>();
|
|
for (int celli = 0; celli < nCells; celli++) {
|
|
allGlobalFacesDef.clear();
|
|
for (int i = 0; i < facesPerCell; i++) {
|
|
allGlobalFacesDef.add(celli * facesPerCell + i);
|
|
}
|
|
for (int i = 0; i < facesPerCell; i++) {
|
|
int nbrCelli = (celli + i + 1) % nCells;
|
|
for (int j = 0; j < facesPerCell; j++) {
|
|
int g = nbrCelli * facesPerCell + j;
|
|
if (!allGlobalFacesDef.contains(g)) allGlobalFacesDef.add(g);
|
|
}
|
|
}
|
|
defResult.add(new HashSet<>(allGlobalFacesDef));
|
|
}
|
|
|
|
List<Set<Integer>> fixResult = new ArrayList<>();
|
|
List<Integer> allGlobalFacesFix = new ArrayList<>();
|
|
Set<Integer> seen = new HashSet<>();
|
|
for (int celli = 0; celli < nCells; celli++) {
|
|
allGlobalFacesFix.clear();
|
|
seen.clear();
|
|
for (int i = 0; i < facesPerCell; i++) {
|
|
int g = celli * facesPerCell + i;
|
|
if (seen.add(g)) allGlobalFacesFix.add(g);
|
|
}
|
|
for (int i = 0; i < facesPerCell; i++) {
|
|
int nbrCelli = (celli + i + 1) % nCells;
|
|
for (int j = 0; j < facesPerCell; j++) {
|
|
int g = nbrCelli * facesPerCell + j;
|
|
if (seen.add(g)) allGlobalFacesFix.add(g);
|
|
}
|
|
}
|
|
fixResult.add(new HashSet<>(allGlobalFacesFix));
|
|
}
|
|
|
|
for (int i = 0; i < nCells; i++) {
|
|
if (!defResult.get(i).equals(fixResult.get(i))) {
|
|
System.err.println("MISMATCH at cell " + i);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== OpenFOAM CFCFaceToCellStencil calcCellStencil Dedup Test ===");
|
|
|
|
// Correctness check on small mesh
|
|
boolean correct = verifyCorrectness(20, 6);
|
|
System.out.println("Correctness (nCells=20, F=6): " + (correct ? "PASS" : "FAIL"));
|
|
if (!correct) System.exit(1);
|
|
|
|
// Performance: C=2000 cells, F=12 faces-per-cell (typical hex mesh)
|
|
int nCells = 2000;
|
|
int facesPerCell = 12;
|
|
|
|
long defOps = calcCellStencilDefective(nCells, facesPerCell);
|
|
long fixOps = calcCellStencilFixed(nCells, facesPerCell);
|
|
|
|
double ratio = (double) defOps / fixOps;
|
|
|
|
System.out.println("Cells: " + nCells + ", faces-per-cell: " + facesPerCell);
|
|
System.out.println("Defective op-count: " + defOps);
|
|
System.out.println("Fixed op-count: " + fixOps);
|
|
System.out.printf ("Ratio (defective/fixed): %.1fx%n", ratio);
|
|
|
|
// Wall-clock timing
|
|
long t0 = System.nanoTime();
|
|
for (int r = 0; r < 3; r++) calcCellStencilDefective(nCells, facesPerCell);
|
|
long defMs = (System.nanoTime() - t0) / 1_000_000 / 3;
|
|
|
|
long t1 = System.nanoTime();
|
|
for (int r = 0; r < 3; r++) calcCellStencilFixed(nCells, facesPerCell);
|
|
long fixMs = (System.nanoTime() - t1) / 1_000_000 / 3;
|
|
|
|
double wallRatio = defMs > 0 && fixMs > 0 ? (double) defMs / fixMs : ratio;
|
|
System.out.printf("Wall time — defective: %dms fixed: %dms ratio: %.1fx%n",
|
|
defMs, fixMs, wallRatio);
|
|
|
|
boolean pass = ratio >= 5.0;
|
|
System.out.println("RESULT: " + (pass ? "PASS" : "FAIL")
|
|
+ " (op-count ratio >= 5.0 required)");
|
|
|
|
if (!pass) System.exit(1);
|
|
}
|
|
}
|