3.6 KiB
vtk-0001: CWE-407 — O(C×npts²) linear dedup scan in vtkStaticCleanPolyData
vtk-0002: CWE-407 — O(numPts×numLabels) linear label-set scan in vtkGeneralizedSurfaceNets3D
vtk-0001
Severity
HIGH
File
Filters/Core/vtkStaticCleanPolyData.cxx:257,293,343,403
Description
vtkStaticCleanPolyData::RequestData() deduplicates point IDs within each
cell using a std::vector<vtkIdType> cellIds maintained per cell. For each
point in the cell the code calls std::find(cellIds.begin(), cellIds.end(), ptId) before appending to the vector. The pattern appears four times for
the four cell types: verts, lines, polys, and triangle strips.
The outer loop iterates over all C cells in the mesh. For each cell the inner loop iterates npts times and each iteration does an O(k) linear scan (where k grows from 0 to npts−1). Total cost per cell: O(npts²). Total cost: O(C × npts²).
For a triangulated surface with C = 10 000 000 triangles this is O(C × 9) ≈ 90 M ops — manageable. But for strip-based or high-valence mesh data (npts up to 64–256) the cost becomes O(C × npts²) = O(C × 65536) ops, a 7 000× regression over the expected O(C × npts) cost.
The source comment even notes: "Just use a vector to keep track of unique ids — it's a small set so find() will execute relatively fast." This is only true when npts is always small, but VTK processes arbitrary meshes.
Root Cause
cellIds is a std::vector<vtkIdType>. Deduplication via linear scan is
O(k) per point. Replacing with std::unordered_set<vtkIdType> reduces the
membership test to O(1) amortized, cutting deduplication from O(npts²) to
O(npts) per cell.
Fix
Replace std::vector<vtkIdType> cellIds with std::unordered_set<vtkIdType> seenIds for the dedup check; accumulate the insertion-ordered result in a
separate std::vector only when building the output cell.
Speedup
At npts = 64 (strip with 64 points): 64²/64 = 64× per cell. At npts = 256: 256× per cell. Asymptotic: O(C × npts²) → O(C × npts).
Affected Operations
vtkStaticCleanPolyData::RequestData()— used as a preprocessing step before virtually every VTK geometry pipeline (surface nets, voronoi, ghost-cell generation, smoothing, etc.)
vtk-0002
Severity
HIGH
File
Filters/Meshing/vtkGeneralizedSurfaceNets3D.cxx:1150
Description
When no explicit labels are provided, RequestData() collects the unique
set of region IDs by iterating all numPts point scalars and calling
std::find(autoLabels.begin(), autoLabels.end(), regionId) before
appending to autoLabels:
for (vtkIdType i = 0; i < numPts; ++i) {
double regionId = static_cast<double>(regions->GetValue(i));
if (regionId >= 0 &&
std::find(autoLabels.begin(), autoLabels.end(), regionId) == autoLabels.end())
{
autoLabels.push_back(regionId);
}
}
Cost: O(numPts × numLabels). For a 50 M-point segmentation volume with 200 tissue classes: 50M × 200 = 10^10 comparisons where O(numPts) suffices.
Root Cause
autoLabels is a std::vector<double>. The uniqueness check is a linear
scan. An std::unordered_set<double> provides O(1) membership.
Fix
Use std::unordered_set<double> for the existence check during collection,
then move the sorted result into a vector if ordering is needed.
Speedup
At numPts = 50 000 000, numLabels = 200: 200× speedup. Asymptotic: O(numPts × numLabels) → O(numPts).
Affected Operations
vtkGeneralizedSurfaceNets3D::RequestData()— called when processing multi-label segmentation volumes (medical imaging, simulation data)