Repository navigation
BUG: C09: Preflight throws in 27 filters #1869
Description
Summary
Some filters throw a C++ exception, or dereference a null pointer, inside preflightImpl. IFilter::preflight has no try/catch, so the exception leaves the filter API and can stop NXRunner or DREAM3D-NX. The correct behavior is an invalid preflight result with an error message. As of develop 419f9db (2026-10-21), this class has 28 open findings in 27 filters: 28 CRASH, 0 WRONG_DATA, 0 MINOR.
Mechanism
IFilter::execute converts an exception that escapes executeImpl into error -2. IFilter::preflight does not do this. It calls preflightImpl directly (IFilter.cpp#L268). Parameter validation checks that a path exists and, for some parameters, its type. It does not check every assumption that preflightImpl makes. The findings fall into four groups:
- Typed reference cast.
getDataRefAs<T>()anddynamic_cast<const T&>throwstd::bad_castwhen the object is a different type. Examples: a float64 array read asFloat32Array; a StringArray or NeighborList in an Attribute Matrix that the code reads asIDataArray; a non-Image geometry read asImageGeom. - Missing Attribute Matrix in a geometry.
getCellDataPath(),getCellDataRef(),getVertexAttributeMatrixDataPath()andgetFaceAttributeMatrixDataPath()throw when the geometry has no such Attribute Matrix.getCellData()->,getVertexAttributeMatrix()->and similar calls dereference a null pointer. A geometry can lose its Attribute Matrix through Delete Data or a file import. - Throwing library calls.
std::filesystem::directory_iterator,nlohmann::json::parse, inicppload(),std::string::substr,fmt::format, and.value()on an emptyResultorstd::optional. - Type dispatch that throws. A parameter accepts a data type (for example bool) that the dispatch function does not support.
Example: the parameter accepts float32 and float64, but preflight reads the array as float32 (ConvertOrientationsToVertexGeometryFilter.cpp#L120-L124):
nx::core::Result<OutputActions> resultOutputActions;
auto& inputOrientationsArray = dataStructure.getDataRefAs<Float32Array>(inputOrientationsArrayPath);
std::vector<usize> inputCompShape = inputOrientationsArray.getComponentShape();getDataRefAs is dynamic_cast<const T&>(getDataRef(path)) (DataStructure.hpp#L401-L405), so a float64 array throws std::bad_cast.
The non-throwing form already exists on develop. AddBadDataFilter gets the pointer and returns an error when it is null (AddBadDataFilter.cpp#L130-L135):
auto* imgGeomPtr = dataStructure.getDataAs<ImageGeom>(pImageGeometryPathValue);
auto* cellAM = imgGeomPtr->getCellData();
if(cellAM == nullptr)
{
return {MakeErrorResult<OutputActions>(-76237, fmt::format("Image geometry must have a valid cell Attribute Matrix"))};
}Rule
preflightImplmust not throw and must not dereference a pointer that can be null. Each failure is an errorResultwith a unique code and a message that names the parameter.- Array-kind contracts (a StringArray, NeighborList or bool array that reaches code for numeric DataArrays) overlap BUG: Enforce array-kind contracts in ArraySelectionParameter #1833 and BUG: Numeric filters can throw after accepting nonnumeric arrays #1821. The fix for those findings must follow what BUG: Enforce array-kind contracts in ArraySelectionParameter #1833 decides. Until then, use a local check that returns a preflight error, and link BUG: Enforce array-kind contracts in ArraySelectionParameter #1833 in a code comment.
- Checks belong in preflight unless they need array values. Remove execute-time checks that the new preflight check makes unreachable.
- Decision needed: should
IFilter::preflightalso get a backstop try/catch, asIFilter::executehas? A backstop keeps the application alive, but it does not replace the per-filter fixes, because the user still gets a generic message.
How to fix
- Replace
getDataRefAs<T>(path)withgetDataAs<T>(path)and return an error when the result isnullptr. If only the tuple count or type is needed, read the array asIDataArrayorIArray. - Replace
getCellDataPath()/getCellDataRef()withgetCellData()and a null check. Do the same for the Vertex, Edge and Face Attribute Matrix accessors (getVertexAttributeMatrix(),getEdgeAttributeMatrix(),getFaceAttributeMatrix()). - When code walks every child of an Attribute Matrix, use a pointer
dynamic_castand handle each array kind.CreateCroppedArrayActionsinCropEdgeGeometryFilter.cppalready creates the correct action for anIDataArray, aStringArrayand anINeighborList. If the filter cannot handle a kind, return a preflight error that names the array. Change the execute path in the same way (for exampleCropImageGeometry.cpp,PadImageGeometry.cpp,ImageGeometryResample.cpp). - For library calls, use the non-throwing overload (
std::filesystem::directory_iterator(path, errorCode),nlohmann::json::parse(text, nullptr, false)withis_discarded()), or wrap the call in try/catch and return an errorResult. - Check a
Resultwithinvalid()before.value(). Check anstd::optionalwithhas_value()before.value(). - Validate numeric parameters (for example a digit count that must be 0 or more) before using them.
- If a parameter accepts a type that the code cannot use (for example bool), narrow the parameter's allowed types. Also check the type in preflight, because some multi-array parameters do not apply their allowed types.
- Error codes: each filter uses one range more negative than -10000 (for example -47500..-47599). Check that a new code is unique with
git grep -nE -- "-NNNN([^0-9]|$)", and also in open PR refs.
How to test
- Write the test first. It must fail on develop. On develop, the test fails with an exception, or it crashes.
- Build the bad input in the test: a float64 array where float32 is assumed, a StringArray or NeighborList in the Cell Data Attribute Matrix, a geometry whose Attribute Matrix was removed, a file with a malformed header, a directory without read permission.
- Call
filter.preflight(...)and assert that the result is invalid and that the first error has the new code. Do not wrap the call inREQUIRE_NOTHROWonly; assert the code. - If the execute path has the same defect, add an execute test too.
- If the filter has more than one algorithm, run the execute test for each, with the existing algorithm-selection test scope (
ForceInCoreAlgorithmGuard/ForceOocAlgorithmGuardinAlgorithmDispatch.hpp). - For a fix that copies a StringArray or NeighborList instead of rejecting it, check the copied values against an independent expected result.
Conventions
- One PR for the whole class. One commit per filter. Commit prefix
BUG:. Add aSigned-off-byline. Do not add AI attribution. - Every fix starts with a unit test that fails on develop. If you cannot reproduce a finding, write that and the reason in the PR and on this issue. Do not force a fix.
- Value checks need an independent expected result (oracle), not the output of the code under test.
- Run ctest serially. Never use
-j, because the tests depend on each other. - Format with clang-format 19.1.1 and the repository
.clang-format. - Do not add per-filter documentation sentences about input requirements. The generated parameter table documents them.
- If a fix touches a filter that has a V&V report (
src/Plugins/<Plugin>/vv/<Filter>.md), update every file of that filter's V&V package (report, deviations, provenance, test tables). V&V documents reference DREAM3D 6.5.171 only. - Rebase on develop. Never merge develop into the branch.
Checklist
- CRASH · AppendImageGeometryFilter · AppendImageGeometryFilter.cpp:123 — Input Image Geometries accepts any path, and preflight calls
getDataRefAs<ImageGeom>, so a Rectilinear Grid, a Vertex Geometry or a DataArray path throwsstd::bad_cast.AppendImageGeometryFilter#3 - CRASH · AppendImageGeometryFilter · AppendImageGeometryFilter.cpp:203 — Preflight dereferences
getCellData()of the destination and input Image Geometries without a null check, so a geometry with no Cell Attribute Matrix crashes (execute has the same code).AppendImageGeometryFilter#4 - CRASH · ApplyTransformationToGeometryFilter · GeometryTransformation.cpp:274 — With Nearest Neighbor resampling, preflight calls
getDataRefAs<IDataArray>on every Cell Data child, so a StringArray or NeighborList throwsstd::bad_cast.ApplyTransformationToGeometryFilter#3 - CRASH · CombineStlFilesFilter · CombineStlFilesFilter.cpp:138 — Preflight uses the throwing
std::filesystem::directory_iteratorconstructor, so a directory without read permission throwsfilesystem_error.CombineStlFilesFilter#6 - CRASH · ComputeFeatureClusteringFilter · ComputeFeatureClusteringFilter.cpp:154 — Preflight calls
getDataRefAs<AttributeMatrix>on the parent of Phases, so a Phases DataArray in a DataGroup or at the root throws.ComputeFeatureClusteringFilter#3 - CRASH · ComputeVertexToTriangleDistancesFilter · ComputeVertexToTriangleDistancesFilter.cpp:108 — Preflight dereferences
getVertexAttributeMatrix()without a null check, so a Vertex Geometry with no Vertex Attribute Matrix crashes (execute line 141 has the same code).ComputeVertexToTriangleDistancesFilter#3 - CRASH · ConvertOrientationsToVertexGeometryFilter · ConvertOrientationsToVertexGeometryFilter.cpp:122 — The parameter accepts float64, but preflight calls
getDataRefAs<Float32Array>, so a float64 Input Orientations DataArray throwsstd::bad_cast.ConvertOrientationsToVertexGeometryFilter#1 - CRASH · CropEdgeGeometryFilter · CropEdgeGeometryFilter.cpp:209 — Preflight dereferences
getVertexAttributeMatrix()andgetEdgeAttributeMatrix()without a null check, so an Edge Geometry with no Vertex or Edge Attribute Matrix crashes.CropEdgeGeometryFilter#3 - CRASH · CropImageGeometryFilter · ImageGeometryCrop.cpp:286 —
dynamic_cast<const IDataArray&>on every Cell Data child throwsstd::bad_castin preflight for a StringArray or NeighborList (execute CropImageGeometry.cpp:381 has the same cast).CropImageGeometryFilter#3 - CRASH · EBSDSegmentFeaturesFilter · EBSDSegmentFeaturesFilter.cpp:129 — Preflight calls
getCellDataPath()without a check, so a grid geometry with no Cell Attribute Matrix (or a stale one) throwsstd::runtime_error.EBSDSegmentFeaturesFilter#4 - CRASH · ExtractPipelineToFileFilter · Dream3dIO.cpp:2430 —
nlohmann::json::parseis not in a try/catch, so a .dream3d file whose /Pipeline/Pipeline string is not valid JSON throwsparse_errorout of preflight.ExtractPipelineToFileFilter#2 - CRASH · FeatureFaceCurvatureFilter · FeatureFaceCurvatureFilter.cpp:147 — Preflight calls
getFaceAttributeMatrixDataPath(), which throws when the Triangle Geometry has no Face Attribute Matrix;triangleMatrixis also not null-checked.FeatureFaceCurvatureFilter#9 - CRASH · InitializeImageGeomCellDataFilter · InitializeImageGeomCellDataFilter.cpp:236 — Cell Arrays accepts all data types, but preflight dispatches through
ExecuteNeighborFunction, which throwsstd::runtime_errorfor a Boolean DataArray (still present in BUG: Check that grid-geometry cell inputs have one tuple per cell #1840).InitializeImageGeomCellDataFilter#2 - CRASH · InterpolatePointCloudToRegularGridFilter · InterpolatePointCloudToRegularGridFilter.cpp:180 — Preflight calls
getCellDataPath()on the Interpolated Image Geometry, which throws when its Cell Attribute Matrix was removed.InterpolatePointCloudToRegularGridFilter#4 - CRASH · PadImageGeometryFilter · PadImageGeometryFilter.cpp:227 —
dynamic_cast<const IDataArray&>on every Cell Data child throwsstd::bad_castin preflight for a StringArray or NeighborList (execute PadImageGeometry.cpp:150 and 153 have the same cast).PadImageGeometryFilter#5 - CRASH · QuickSurfaceMeshFilter · QuickSurfaceMeshFilter.cpp:176 — Preflight calls
gridGeom.getCellData()->getNumberOfTuples()without a null check, so a grid geometry with no Cell Attribute Matrix crashes.QuickSurfaceMeshFilter#7 - CRASH · ReadEnsembleInfoFilter · ReadEnsembleInfoFilter.cpp:108 —
inputIni.load()and.as<int32>()are not in a try/catch, so a malformed line orNumber_Phases = abcthrows out of preflight.ReadEnsembleInfoFilter#2 - CRASH · ReadHDF5DatasetFilter · ReadHDF5DatasetFilter.cpp:288 — In the 'already exists' branch, preflight calls
pSelectedAttributeMatrixValue.value()when the optional is empty, which throwsstd::bad_optional_access.ReadHDF5DatasetFilter#1 - CRASH · ReadVtkStructuredPointsFilter · ReadVtkStructuredPoints.cpp:1028 —
readFileruns in preflight and callsconvertResultI32.value()without a check, so a non-numeric or missing section count (for examplePOINT_DATA abc) throws.ReadVtkStructuredPointsFilter#3 - CRASH · RegularGridSampleSurfaceMeshFilter · RegularGridSampleSurfaceMeshFilter.cpp:217 — With Use existing geometry, preflight calls
getCellDataPath()andgetCellDataRef(), which throw when the Image Geometry has no Cell Attribute Matrix.RegularGridSampleSurfaceMeshFilter#6 - CRASH · RemoveFlaggedEdgesFilter · DataArrayUtilities.cpp:507 — With Copy All,
CreateDataArrayActionscallsgetDataRefAs<IDataArray>on every child of the Edge or Vertex Attribute Matrix, so a StringArray or NeighborList throws in preflight.RemoveFlaggedEdgesFilter#3 - CRASH · RemoveFlaggedTrianglesFilter · DataArrayUtilities.cpp:507 — With Copy All,
CreateDataArrayActionscallsgetDataRefAs<IDataArray>on every child of the Face or Vertex Attribute Matrix, so a StringArray or NeighborList throws in preflight.RemoveFlaggedTrianglesFilter#4 - CRASH · RemoveFlaggedVerticesFilter · RemoveFlaggedVerticesFilter.cpp:94 — Preflight calls
getVertexAttributeMatrixDataPath()before the null check at line 118, so a missing Vertex Attribute Matrix throws; line 130 also throwsstd::bad_castfor a StringArray or NeighborList.RemoveFlaggedVerticesFilter#3 - CRASH · ResampleImageGeomFilter · ImageGeometryResample.cpp:320 —
dynamic_cast<const IDataArray&>on every Cell Data child throwsstd::bad_castin preflight for a StringArray or NeighborList (execute line 440 has the same cast).ResampleImageGeomFilter#5 - CRASH · ResampleRectGridToImageGeomFilter · ResampleRectGridToImageGeomFilter.cpp:137 — Preflight calls
rectGridGeom.getCellData()->getName()without a null check, so a Rectilinear Grid with no Cell Attribute Matrix crashes.ResampleRectGridToImageGeomFilter#1 - CRASH · WriteGBCDGMTFileFilter · WriteGBCDGMTFileFilter.cpp:103 —
fileName.substr(fileName.size() - 2)throwsstd::out_of_rangefor an output file stem shorter than 2 characters (for examplea.dat); the_1logic is also inverted and its result is not used.WriteGBCDGMTFileFilter#2 - CRASH · WriteINLFileFilter · WriteINLFileFilter.cpp:107 — Material Names accepts any object, and preflight dereferences
getDataAs<IArray>(...)without a null check, so an Attribute Matrix path gives a null-pointer dereference.WriteINLFileFilter#1 - CRASH · WriteImageFilter · WriteImageFilter.cpp:303 — With Total Number of Index Digits = -1 and more than one slice,
CreateIndexStringgets a width of about 1.8e19 andfmtthrowsformat_errorin preflight.WriteImageFilter#1
Already fixed or in progress
- BUG: Fix defects in seven SimplnxCore filters #1763 (commit 8bed04c, on develop):
CreateDataArrayAdvancedFilter#2,InitializeDataFilter#2 - BUG: Fix RectGrid bounds validation during preflight #1771 (commit fe6c4a1, on develop):
CreateGeometryFilter#1 - BUG: Check that grid-geometry cell inputs have one tuple per cell #1840 (open PR):
AlignSectionsMutualInformationFilter#1
Related issues
- BUG: Enforce array-kind contracts in ArraySelectionParameter #1833 BUG: Enforce array-kind contracts in ArraySelectionParameter. The array-kind findings must follow its decision.
- BUG: Numeric filters can throw after accepting nonnumeric arrays #1821 BUG: Numeric filters can throw after accepting nonnumeric arrays.
Out of scope
- Findings in filters that have a V&V report are not in this issue. There are 3: CAxisSegmentFeaturesFilter, NeighborOrientationCorrelationFilter, RotateSampleRefFrameFilter. A separate V&V branch handles them.
- The parameter-level array-kind contract is the work of BUG: Enforce array-kind contracts in ArraySelectionParameter #1833. This issue fixes the filters only.
- Every finding is a candidate. It was found and re-checked by reading the code at develop 419f9db, not by running it.
Activity
[-]BUG: Preflight throws in 27 filters[/-][+]BUG: C09: Preflight throws in 27 filters[/+]