Skip to content

BUG: C09: Preflight throws in 27 filters #1869

Open

Description

@imikejackson

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:

  1. Typed reference cast. getDataRefAs<T>() and dynamic_cast<const T&> throw std::bad_cast when the object is a different type. Examples: a float64 array read as Float32Array; a StringArray or NeighborList in an Attribute Matrix that the code reads as IDataArray; a non-Image geometry read as ImageGeom.
  2. Missing Attribute Matrix in a geometry. getCellDataPath(), getCellDataRef(), getVertexAttributeMatrixDataPath() and getFaceAttributeMatrixDataPath() 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.
  3. Throwing library calls. std::filesystem::directory_iterator, nlohmann::json::parse, inicpp load(), std::string::substr, fmt::format, and .value() on an empty Result or std::optional.
  4. 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

How to fix

  1. Replace getDataRefAs<T>(path) with getDataAs<T>(path) and return an error when the result is nullptr. If only the tuple count or type is needed, read the array as IDataArray or IArray.
  2. Replace getCellDataPath() / getCellDataRef() with getCellData() and a null check. Do the same for the Vertex, Edge and Face Attribute Matrix accessors (getVertexAttributeMatrix(), getEdgeAttributeMatrix(), getFaceAttributeMatrix()).
  3. When code walks every child of an Attribute Matrix, use a pointer dynamic_cast and handle each array kind. CreateCroppedArrayActions in CropEdgeGeometryFilter.cpp already creates the correct action for an IDataArray, a StringArray and an INeighborList. If the filter cannot handle a kind, return a preflight error that names the array. Change the execute path in the same way (for example CropImageGeometry.cpp, PadImageGeometry.cpp, ImageGeometryResample.cpp).
  4. For library calls, use the non-throwing overload (std::filesystem::directory_iterator(path, errorCode), nlohmann::json::parse(text, nullptr, false) with is_discarded()), or wrap the call in try/catch and return an error Result.
  5. Check a Result with invalid() before .value(). Check an std::optional with has_value() before .value().
  6. Validate numeric parameters (for example a digit count that must be 0 or more) before using them.
  7. 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.
  8. 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 in REQUIRE_NOTHROW only; 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 / ForceOocAlgorithmGuard in AlgorithmDispatch.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 a Signed-off-by line. 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 throws std::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 throws std::bad_cast. ApplyTransformationToGeometryFilter#3
  • CRASH · CombineStlFilesFilter · CombineStlFilesFilter.cpp:138 — Preflight uses the throwing std::filesystem::directory_iterator constructor, so a directory without read permission throws filesystem_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 throws std::bad_cast. ConvertOrientationsToVertexGeometryFilter#1
  • CRASH · CropEdgeGeometryFilter · CropEdgeGeometryFilter.cpp:209 — Preflight dereferences getVertexAttributeMatrix() and getEdgeAttributeMatrix() 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 throws std::bad_cast in 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) throws std::runtime_error. EBSDSegmentFeaturesFilter#4
  • CRASH · ExtractPipelineToFileFilter · Dream3dIO.cpp:2430 — nlohmann::json::parse is not in a try/catch, so a .dream3d file whose /Pipeline/Pipeline string is not valid JSON throws parse_error out of preflight. ExtractPipelineToFileFilter#2
  • CRASH · FeatureFaceCurvatureFilter · FeatureFaceCurvatureFilter.cpp:147 — Preflight calls getFaceAttributeMatrixDataPath(), which throws when the Triangle Geometry has no Face Attribute Matrix; triangleMatrix is also not null-checked. FeatureFaceCurvatureFilter#9
  • CRASH · InitializeImageGeomCellDataFilter · InitializeImageGeomCellDataFilter.cpp:236 — Cell Arrays accepts all data types, but preflight dispatches through ExecuteNeighborFunction, which throws std::runtime_error for 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 throws std::bad_cast in 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 or Number_Phases = abc throws 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 throws std::bad_optional_access. ReadHDF5DatasetFilter#1
  • CRASH · ReadVtkStructuredPointsFilter · ReadVtkStructuredPoints.cpp:1028 — readFile runs in preflight and calls convertResultI32.value() without a check, so a non-numeric or missing section count (for example POINT_DATA abc) throws. ReadVtkStructuredPointsFilter#3
  • CRASH · RegularGridSampleSurfaceMeshFilter · RegularGridSampleSurfaceMeshFilter.cpp:217 — With Use existing geometry, preflight calls getCellDataPath() and getCellDataRef(), which throw when the Image Geometry has no Cell Attribute Matrix. RegularGridSampleSurfaceMeshFilter#6
  • CRASH · RemoveFlaggedEdgesFilter · DataArrayUtilities.cpp:507 — With Copy All, CreateDataArrayActions calls getDataRefAs<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, CreateDataArrayActions calls getDataRefAs<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 throws std::bad_cast for a StringArray or NeighborList. RemoveFlaggedVerticesFilter#3
  • CRASH · ResampleImageGeomFilter · ImageGeometryResample.cpp:320 — dynamic_cast<const IDataArray&> on every Cell Data child throws std::bad_cast in 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) throws std::out_of_range for an output file stem shorter than 2 characters (for example a.dat); the _1 logic 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, CreateIndexString gets a width of about 1.8e19 and fmt throws format_error in preflight. WriteImageFilter#1

Already fixed or in progress

Related issues

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

  1. changed the title [-]BUG: Preflight throws in 27 filters[/-] [+]BUG: C09: Preflight throws in 27 filters[/+] on Oct 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions