openfoam+stella: 5-MOAD scan; openfoam-0002 CWE-407, stella CLEAN

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.
This commit is contained in:
russell@unturf.com 2026-03-31 21:55:06 -04:00
parent 38b5c504cf
commit edf083b2c4
5 changed files with 593 additions and 0 deletions

View file

@ -0,0 +1,82 @@
# openfoam-0002: CFCFaceToCellStencil calcCellStencil allGlobalFaces dedup O(C*F*G)
## Target
OpenFOAM-dev (https://github.com/OpenFOAM/OpenFOAM-dev)
## File
`src/finiteVolume/fvMesh/extendedStencil/faceToCell/globalIndexStencils/CFCFaceToCellStencil.C`
## Function
`Foam::CFCFaceToCellStencil::calcCellStencil(labelListList&)`
## MOAD
0001 — CWE-407 Algorithmic Complexity
## Severity
HIGH
## Pattern
```cpp
DynamicList<label> allGlobalFaces(100);
forAll(globalCellFaces, celli) // O(C) — all mesh cells
{
allGlobalFaces.clear();
// ... add own faces ...
forAll(cFaces, i) // O(F) — faces per cell (~6-12 hex)
{
const cell& nbrFaces = ...;
forAll(nbrFaces, j) // O(F) — neighbour faces
{
label nbrGlobalI = ...;
// Check if already there. Note:should use hashset?
if (findIndex(allGlobalFaces, nbrGlobalI) == -1) // O(G)
{
allGlobalFaces.append(nbrGlobalI);
}
}
}
}
```
`findIndex` is a linear scan over `allGlobalFaces` (a `DynamicList<label>`). Per cell, G grows to O(F + F*F_nbr) = O(F²) face entries. The total complexity is O(C * F² * G) = O(C * F³) vs O(C * F²) with a hash set.
Even the OpenFOAM developers noticed: comment reads **"Note:should use hashset?"** — confirmed defect, never fixed.
## Complexity
| Version | Complexity | At C=1M cells, F=12 |
|---------|-----------|---------------------|
| Defective | O(C * F * G), G=O(F²) | ~1.7 billion findIndex calls |
| Fixed | O(C * F) with O(1) set ops | ~144 million ops |
| Speedup | ~12x | (theoretical at F=12) |
## Fix
Replace `DynamicList<label> allGlobalFaces` dedup-via-`findIndex` with a `labelHashSet seen` for O(1) membership. `allGlobalFaces` is kept for ordered output.
```cpp
DynamicList<label> allGlobalFaces(100);
labelHashSet seen(100);
forAll(globalCellFaces, celli)
{
allGlobalFaces.clear();
seen.clear();
// ... use seen.insert(globalI) instead of findIndex check ...
}
```
## Impact
`CFCFaceToCellStencil` is used to build stencils for extended interpolation schemes (LUST, upwind-biased schemes, limiters) on unstructured meshes. It runs once per mesh load or topology change. For large industrial CFD cases with millions of cells this startup phase can take tens of seconds. Patch eliminates O(F²) factor per cell.
## MOADs 0002-0005
- **0002 Intertangle**: `objectRegistry` is a god object but uses `HashTable` — CLEAN.
- **0003 Leaked Context**: No thread-local request identity. OpenFOAM uses MPI, not threads. `OFstreamCollator` spawns one background write thread with `std::mutex` — CLEAN.
- **0004 CWE-312**: No credentials in log streams found. Zoltan parameter logging is config key/value only, not secrets — CLEAN.
- **0005 Thundering Herd**: Lazy singletons (`unitsDictPtr_`, `dimensionedConstantsDictPtr_`) have no mutex but OpenFOAM is single-threaded (MPI-parallel) — no thundering herd risk — CLEAN.

View file

@ -0,0 +1,104 @@
# UNDF:
--- a/src/finiteVolume/fvMesh/extendedStencil/faceToCell/globalIndexStencils/CFCFaceToCellStencil.C
+++ b/src/finiteVolume/fvMesh/extendedStencil/faceToCell/globalIndexStencils/CFCFaceToCellStencil.C
@@ -28,6 +28,7 @@ License
#include "CFCFaceToCellStencil.H"
#include "syncTools.H"
+#include "HashSet.H"
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
@@ -128,37 +129,42 @@ void Foam::CFCFaceToCellStencil::calcCellStencil
// Determine faces of cellCells in global numbering
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- DynamicList<label> allGlobalFaces(100);
+ DynamicList<label> allGlobalFaces(100);
+ labelHashSet seen(100);
globalCellFaces.setSize(mesh().nCells());
forAll(globalCellFaces, celli)
{
const cell& cFaces = mesh().cells()[celli];
allGlobalFaces.clear();
+ seen.clear();
// My faces first
forAll(cFaces, i)
{
label facei = cFaces[i];
if
(
mesh().isInternalFace(facei)
|| validBFace[facei-mesh().nInternalFaces()]
)
{
- allGlobalFaces.append(globalNumbering().toGlobal(facei));
+ label globalI = globalNumbering().toGlobal(facei);
+ if (seen.insert(globalI))
+ {
+ allGlobalFaces.append(globalI);
+ }
}
}
// faces of neighbouring cells second
forAll(cFaces, i)
{
label facei = cFaces[i];
if (mesh().isInternalFace(facei))
{
label nbrCelli = own[facei];
if (nbrCelli == celli)
{
nbrCelli = nei[facei];
}
const cell& nbrFaces = mesh().cells()[nbrCelli];
forAll(nbrFaces, j)
{
label nbrFacei = nbrFaces[j];
if
(
mesh().isInternalFace(nbrFacei)
|| validBFace[nbrFacei-mesh().nInternalFaces()]
)
{
label nbrGlobalI = globalNumbering().toGlobal(nbrFacei);
- // Check if already there. Note:should use hashset?
- if (findIndex(allGlobalFaces, nbrGlobalI) == -1)
+ if (seen.insert(nbrGlobalI))
{
allGlobalFaces.append(nbrGlobalI);
}
}
}
}
else
{
const labelList& nbrGlobalFaces =
neiGlobal[facei-mesh().nInternalFaces()];
forAll(nbrGlobalFaces, j)
{
label nbrGlobalI = nbrGlobalFaces[j];
- // Check if already there. Note:should use hashset?
- if (findIndex(allGlobalFaces, nbrGlobalI) == -1)
+ if (seen.insert(nbrGlobalI))
{
allGlobalFaces.append(nbrGlobalI);
}
}
}
}
globalCellFaces[celli] = allGlobalFaces;
}
}

View file

@ -0,0 +1,184 @@
/**
* 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);
}
}

View file

@ -0,0 +1,184 @@
/**
* 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);
}
}

View file

@ -0,0 +1,39 @@
# Stella (Atari 2600 Emulator) — 5-MOAD Scan
Target: https://github.com/stella-emu/stella
Scan date: 2026-03-31
## MOAD-0001 — CWE-407 Algorithmic Complexity: CLEAN
Our hot paths were the primary concern for a cycle-accurate emulator running at 1.19 MHz.
**BreakpointMap** (`src/debugger/BreakpointMap.hxx`): Uses `std::unordered_map<Breakpoint, uInt32, BreakpointHash>` — O(1) lookup per CPU instruction. CLEAN.
**TrapArray** (`src/debugger/TrapArray.hxx`): Uses `std::array<uInt8, 0x10000>` — O(1) direct address index. CLEAN.
**TIA dispatch** (`src/emucore/tia/`): No linear scan in cycle dispatch. CLEAN.
**Watchpoints** (`src/debugger/DebuggerParser.cxx`): `myWatches` is a `StringList` iterated in `showWatches()` — only called on debugger step (interactive), not per CPU instruction. Bounded watch count (user-set). CLEAN.
No O(N) container scan found inside our per-instruction or per-cycle execution paths.
## MOAD-0002 — Intertangle / God Object: CLEAN
`OSystem` aggregates console, settings, sound, and video — classic god object shape.
However, all subsystems are accessed via typed references with clear ownership. No shared mutable global state shared across independent subsystems. CLEAN.
## MOAD-0003 — Leaked Context / ThreadLocal: CLEAN
Stella is single-threaded. No `thread_local`, no `ThreadLocal`, no `pthread_key_*`. CLEAN.
## MOAD-0004 — CWE-312 Credentials in Logs: CLEAN
Stella is an offline emulator with no network connectivity, no authentication, no credential handling. No log paths found containing passwords, tokens, or keys. CLEAN.
## MOAD-0005 — Thundering Herd: CLEAN
Stella is single-threaded. No concurrent cache initialization patterns. CLEAN.
## Summary
All 5 MOADs: **CLEAN**