Skip to content

BUG: C10: Parameter DataType wider than the execute cast in 9 filters #1870

Open

Description

@imikejackson

Summary

Some filters accept more DataTypes or values in their parameters than their execute code can handle. Preflight passes, and then execute casts a value or a DataArray to a type that is too narrow, or dispatches to a function that rejects the type. The result is a crash, undefined behavior that gives wrong output, or an unclear execute error.

As of develop 419f9db (2026-10-21), there are 9 open findings in 9 filters: 2 CRASH, 4 WRONG_DATA, 3 MINOR. Each finding is a candidate. It was found and re-checked by reading code, not by running it.

This class overlaps the open issues #1833 ("Enforce array-kind contracts in ArraySelectionParameter") and #1821. The parameter-level part of the fix must follow what #1833 decides.

Mechanism

The class has four sub-patterns:

  1. Numeric parameter cast to the array type without a range check (ThresholdImageFilter, ThresholdMaximumConnectedComponentsImageFilter). A float64 parameter is cast with static_cast<T> to the pixel type. A value outside the range of T (for example 1000 for uint8, or -1.0, or NaN) is undefined behavior in C++. The threshold then uses an unexpected value.
  2. Random distribution type narrower than the array type (InitializeDataFilter, InitializeImageGeomCellDataFilter). The code uses std::uniform_int_distribution<int64> or std::uniform_int_distribution<> (int) for every integer type. For UInt32, Int64 or UInt64 the limits narrow, the lower bound becomes larger than the upper bound, and the values are garbage.
  3. Allowed DataTypes include a type that execute does not handle (FlyingEdges3DFilter, WriteVtkStructuredPointsFilter, MapPointCloudToRegularGridFilter, UncertainRegularGridSampleSurfaceMeshFilter). The parameter allows boolean or uint8 or a small integer type. Execute then calls a dispatch function that throws for bool, casts the DataArray to BoolArray and dereferences null, or stores face indices in the label type so that they wrap.
  4. The parameter does not enforce its own allowed DataTypes (CombineTransformationMatricesFilter). MultiArraySelectionParameter::validatePaths checks existence, array type and component shape, but not m_AllowedDataTypes. A float64 DataArray passes preflight, and execute throws bad_cast.

Example of sub-pattern 1: ThresholdImageFilter casts the three float64 parameters to the pixel type with no check:

ThresholdImageFilter.cpp#L27-L34

  template <class T, class U>
  auto makeMapOp() const
  {
    const T lowerT = static_cast<T>(lower);
    const T upperT = static_cast<T>(upper);
    const U outsideValueU = static_cast<U>(outsideValue);
    return [lowerT, upperT, outsideValueU](T value) -> U { return (value < lowerT || value > upperT) ? outsideValueU : static_cast<U>(value); };
  }

With uint8 input and Upper = 1000 (meant as "no upper limit"), static_cast<uint8>(1000.0) is undefined behavior. On common platforms the result is 232, so pixels above 232 are set to Outside Value (not confirmed by running it).

A fix of sub-pattern 3 is in open PR #1766 for ComputeNumFeaturesFilter. The PR narrows the parameter to the type that execute uses:

  params.insert(std::make_unique<ArraySelectionParameter>(k_FeaturePhasesArrayPath_Key, "Feature Phases", "Array specifying which Ensemble each Feature belongs",
                                                          DataPath({"DataContainer", "Feature Data", "Phases"}), ArraySelectionParameter::AllowedTypes{DataType::int32},
                                                          ArraySelectionParameter::AllowedComponentShapes{{1}}));

Rule

Every DataType and every value that a parameter accepts must work in execute, or preflight must reject it with a clear error.

Decision needed for the parameter-level part (sub-patterns 3 and 4). Follow what #1833 decides. The options are:

  • Narrow the parameter. Remove the DataTypes that execute does not support (for example use GetAllNumericTypes() instead of GetAllDataTypes()). This is the smallest change.
  • Support the type in execute. For example write bool as 0/1 in WriteVtkStructuredPointsFilter, or use MaskCompareUtilities::InstantiateMaskCompare in MapPointCloudToRegularGridFilter.
  • Enforce the declared contract in the parameter. Make MultiArraySelectionParameter::validatePaths check m_AllowedDataTypes, as ArraySelectionParameter does for IDataArray selections. This is the shared fix that BUG: Enforce array-kind contracts in ArraySelectionParameter #1833 tracks.

For sub-patterns 1 and 2 no shared decision is needed. Values must be range-checked in preflight (the DataType of the selected DataArray is known in preflight), and execute must use a distribution or comparison type that holds the full range of the array type.

How to fix

  1. Sub-pattern 1: in preflight, read the DataType of the selected DataArray. Reject NaN and values outside [std::numeric_limits<T>::lowest(), std::numeric_limits<T>::max()] with a clear error, or clamp them in float64 before the cast. Compare in float64 where the semantics allow it.
  2. Sub-pattern 2: use std::uniform_int_distribution<T> for integral T. The C++ standard does not allow 8-bit types in uniform_int_distribution, so use int16/uint16 for int8/uint8. Range-check the user range in preflight.
  3. Sub-pattern 3: choose an option from the Rule after BUG: Enforce array-kind contracts in ArraySelectionParameter #1833 decides. For UncertainRegularGridSampleSurfaceMeshFilter, store face indices as usize (or MeshIndexType) whatever the label type; this does not depend on BUG: Enforce array-kind contracts in ArraySelectionParameter #1833. The code is in src/simplnx/Utilities/SampleSurfaceMesh.cpp. At 419f9db only UncertainRegularGridSampleSurfaceMesh derives from SampleSurfaceMesh.
  4. Sub-pattern 4: fix MultiArraySelectionParameter::validatePaths as part of the BUG: Enforce array-kind contracts in ArraySelectionParameter #1833 work, or narrow the dispatch in CombineTransformationMatricesFilter until then.
  5. Error codes: use a new code in the filter's existing range. Filter error codes are more negative than -10000, and each filter uses one range (first code -47500 means all codes -47500..-47599). Check that the code is unique with git grep -nE -- "-NNNN([^0-9]|$)" (do not use \b) and also in the open PR refs.
  6. Remove any execute-time check that the new preflight check makes unreachable. Do not keep duplicate checks.

How to test

  • Write the test first. It must fail on develop.
  • Sub-pattern 1: preflight test with uint8 input and an out-of-range or NaN parameter value. Assert the new error code, or, if the value is clamped, an execute test whose expected output is computed by hand.
  • Sub-pattern 2: execute test with Random fill on UInt32, Int64 and UInt64 DataArrays. Assert that every value is inside the requested range. Use a fixed seed.
  • Sub-pattern 3: run the filter with the DataType that fails today (bool, uint8, int8). Assert either a clear preflight error or correct output.
  • Sub-pattern 4: preflight test with float64 matrices. Assert a preflight error, not execute error -2.
  • Output values need an independent expected result (oracle), not the code's own output.

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 with the reason in the PR and on this issue. Do not force a fix.
  • Value checks need an independent expected result (oracle), not the code's own output.
  • Run ctest serially. Never use -j; the tests depend on each other.
  • Format with clang-format 19.1.1 and the repository .clang-format.
  • Do NOT add documentation sentences about input requirements to the filter pages. 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 · MapPointCloudToRegularGridFilter · MapPointCloudToRegularGridFilter.cpp:74 — The Mask allows uint8, but Manual mode casts it to BoolArray and dereferences null before the check. MapPointCloudToRegularGridFilter#1
  • CRASH · UncertainRegularGridSampleSurfaceMeshFilter · SampleSurfaceMesh.cpp:253 — Face indices are stored as the label type; int8/uint8 labels wrap above 127/255 faces and index face bounds out of bounds. UncertainRegularGridSampleSurfaceMeshFilter#2
  • WRONG_DATA · InitializeDataFilter · InitializeData.cpp:275 — Random fill uses uniform_int_distribution<int64>; for UInt64 the maximum becomes -1, so values are garbage. InitializeDataFilter#4
  • WRONG_DATA · InitializeImageGeomCellDataFilter · InitializeImageGeomCellData.cpp:83 — Random fill uses uniform_int_distribution<> (int); UInt32, Int64 and UInt64 limits narrow, so values are garbage. InitializeImageGeomCellDataFilter#3
  • WRONG_DATA · ThresholdImageFilter · ThresholdImageFilter.cpp:30 — Lower, Upper and Outside Value are cast to the pixel type unchecked; Upper = 1000 on uint8 is undefined and shifts the threshold. ThresholdImageFilter#1
  • WRONG_DATA · ThresholdMaximumConnectedComponentsImageFilter · ImageProcessingFilterUtilities.hpp:4549 — Upper Boundary is clamped only from above; -1.0 or NaN on uint8 is undefined, and 64-bit types cast before std::min. ThresholdMaximumConnectedComponentsImageFilter#1
  • MINOR · CombineTransformationMatricesFilter · MultiArraySelectionParameter.cpp:124 — MultiArraySelectionParameter ignores allowed DataTypes; float64 matrices pass preflight, then execute fails with error -2. CombineTransformationMatricesFilter#1
  • MINOR · FlyingEdges3DFilter · FlyingEdges3D.cpp:100 — The parameter allows bool, but ExecuteNeighborFunction throws for bool; the run fails with error -2. FlyingEdges3DFilter#4
  • MINOR · WriteVtkStructuredPointsFilter · WriteVtkStructuredPoints.cpp:427 — The parameter allows bool, but ExecuteDataFunctionNoBool throws for a BoolArray; the run fails with an unclear error. WriteVtkStructuredPointsFilter#1

Already fixed or in progress

These findings of this class are fixed on develop or in an open PR. They are not in the checklist.

Related issues

Out of scope

Activity

  1. changed the title [-]BUG: Parameter DataType wider than the execute cast in 9 filters[/-] [+]BUG: C10: Parameter DataType wider than the execute cast in 9 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