Skip to content

BUG: C02: Component shape not declared in 6 filters #1862

Open

Description

@imikejackson

Summary

Some filters read an input DataArray with a fixed number of components (usually 1, sometimes a fixed element count), but the parameter that selects the DataArray does not declare that component shape. Preflight then accepts a DataArray with a different number of components. The algorithm reads the values with the wrong stride, or uses the total number of values as the number of tuples. This gives an out-of-bounds read or write, or wrong output with no error.

Open findings as of develop 419f9db (2026-10-21): 3 CRASH, 3 WRONG_DATA, 0 MINOR, in 6 filters. Each finding is a candidate: it was found and re-checked by reading code, not by running it.

Mechanism

The algorithm assumes 1 component per tuple, but the ArraySelectionParameter accepts any component shape. Example from FlyingEdges3DFilter: the parameter declares only the data types.

FlyingEdges3DFilter.cpp#L65-L66

  params.insert(std::make_unique<ArraySelectionParameter>(k_SelectedDataArrayPath_Key, "Data Array to Contour", "This is the data that will be checked for the contouring iso value", DataPath{},
                                                          GetAllDataTypes()));

A uint8 RGB DataArray with 3 components and one tuple per Image Geometry cell passes preflight. The contour algorithm reads it as 3 times more scalar values, so the surface is wrong. A tuple-count check (for example in PR #1840) does not find this, because the number of tuples is correct.

A related form: the algorithm uses getSize() (tuples x components) as the number of tuples. In AddBadData, a 3-component DataArray with one tuple per cell drives writes up to 3 times the cell count.

Fixed form (PR #1763, commit 8bed04c): WriteStlFileFilter now declares the component shape that its algorithm reads. ArraySelectionParameter::validate then rejects any other shape before preflight runs.

WriteStlFileFilter.cpp#L75-L76

  params.insert(std::make_unique<ArraySelectionParameter>(k_FeatureIdsPath_Key, "Face labels", "The triangle feature ids array to order/index files by", DataPath{},
                                                          ArraySelectionParameter::AllowedTypes{DataType::int32}, ArraySelectionParameter::AllowedComponentShapes{{2}}));

Two findings do not select the array through a parameter, so the parameter cannot hold the rule:

  • ImportFijiMontageFilter reads tiles from files. The tile component count is known from the image metadata in preflight, but nothing checks it before grayscale conversion.
  • ApplyTransformationToGeometryFilter checks that the pre-computed matrix has 16 elements only inside the Image Geometry branch. For node geometries, execute reads 16 values from a DataArray of any size.

Rule

  • If an algorithm assumes a component count or stride for an input DataArray, the ArraySelectionParameter or MultiArraySelectionParameter that selects it MUST declare that shape with AllowedComponentShapes{{N}}. The parameter is the check. Parameter validation runs before the filter's preflight, so do not add a second preflight or execute check for the same component shape.
  • Layering: structure (component shape, DataType) goes in the parameter. Relationships between inputs (sibling tuple counts, tuple count vs geometry element count) go in preflight. Checks that need array values go in execute.
  • Algorithms use getNumberOfTuples() when they mean tuples, not getSize().
  • When the input does not come from a parameter (a file, or a mode-dependent input), the check goes in preflight.
  • Decision needed for ComputeArrayHistogramFilter: the component limit applies only when Use Mask Array is on, so a parameter-level shape cannot express it. Options: (a) restrict Input Data Arrays to {{1}} for all modes; (b) return a preflight error for multi-component inputs only when Use Mask Array is on; (c) apply each mask value to all components of its tuple in the algorithm.

How to fix

  1. Add AllowedComponentShapes{{1}} (or the shape the algorithm reads) to the parameter. Both ArraySelectionParameter and MultiArraySelectionParameter have this argument at 419f9db, and both validate it.
  2. In the algorithm, replace getSize() with getNumberOfTuples() where the code means tuples (AddBadData.cpp lines 135 and 232).
  3. For NormalizeImageFilter, pass requireScalar = true to ImageProcessing::PreflightFullOverwriteImageFilter (the argument exists at 419f9db), or add AllowedComponentShapes{{1}} to the parameter. Use one of the two, not both.
  4. For ImportFijiMontageFilter, check ImageMetadata::numComponents in preflightImpl when Convert To GrayScale is on. Decide if a tile with fewer than 3 components is an error or a warning plus skip. Also add a component check to PreflightColorToGrayScale (src/simplnx/Utilities/ImageProcessing/ColorToGrayScale.cpp); ReadImageStackFilter and ConvertColorToGrayScaleFilter also call it.
  5. For ApplyTransformationToGeometryFilter, move the 16-element check (error -82019) out of the if(imageGeomPtr != nullptr) branch so that it runs for every geometry type.
  6. Remove any execute-time component check that the new parameter or preflight check makes unreachable.
  7. New filter error codes must be more negative than -10000 and stay in the filter's existing range. Check that each new code is unique with git grep -nE -- "-NNNN([^0-9]|$)", and also in open PR branches.

How to test

  • Write the test first. It must fail on develop.
  • Parameter rule: build a DataStructure with a DataArray that has the correct number of tuples but the wrong number of components (for example 3 components on an Image Geometry with 24 cells and 24 tuples). Run preflight. Require a preflight error. For a parameter-level shape the error is k_Validate_TupleShapeValue from ArraySelectionParameter::validate.
  • ApplyTransformationToGeometryFilter: a Triangle Geometry with Pre-Computed Transformation Matrix set to a Float32 DataArray of 3 tuples x 1 component. Require preflight error -82019.
  • ImportFijiMontageFilter: a TileConfiguration with 8-bit grayscale tiles (1 component, uint8) and Convert To GrayScale on. Require the decided preflight error or warning. Run the test with both in-core and out-of-core stores, because the out-of-core path is the one that over-reads.
  • If a finding cannot be reproduced, write that in the PR and on this issue, with the reason. Do not force a fix.
  • If a fix changes output values, compare them with an independent expected result (oracle), not with 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. No AI attribution.
  • Every fix starts with a unit test that fails on develop.
  • Value checks need an independent expected result (oracle), not the code's own output.
  • Run ctest serially. Never use -j (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 · AddBadDataFilter · AddBadDataFilter.cpp:76 — Boundary Euclidean Distances has no component shape; a 3-component int32 DataArray with one tuple per cell passes, and the algorithm uses getSize() as the point count, so it writes up to 3 times the cell count. AddBadDataFilter#3
  • CRASH · ApplyTransformationToGeometryFilter · GeometryTransformation.cpp:84 — the 16-element check for the pre-computed matrix runs only for Image Geometry; with a Triangle (or other node) Geometry and a 3-element Float32 DataArray, execute reads 16 values out of bounds. ApplyTransformationToGeometryFilter#6
  • CRASH · ImportFijiMontageFilter · ImportFijiMontageFilter.cpp:338 — with Convert To GrayScale on and ordinary 8-bit grayscale tiles (1 component), only the data type is checked; the out-of-core luminosity path reads 3 values per tuple past the chunk buffer (in-core it throws). ImportFijiMontageFilter#1
  • WRONG_DATA · ComputeArrayHistogramFilter · ComputeArrayHistogram.cpp:653 — with Use Mask Array on and a 3-component input, the count is tuples but the reader reads flat values, so mask value i gates value i, not tuple i; the histogram is wrong. Decision needed (see Rule). ComputeArrayHistogramFilter#6
  • WRONG_DATA · FlyingEdges3DFilter · FlyingEdges3DFilter.cpp:65 — Data Array to Contour has no component shape; a 3-component Cell Data DataArray (for example IPF Colors) is read as interleaved scalars, so the surface is wrong. FlyingEdges3DFilter#3
  • WRONG_DATA · NormalizeImageFilter · NormalizeImageFilter.cpp:91 — preflight does not pass requireScalar and Input Cell Data has no component shape; a 3-component RGB DataArray is normalized with one mean and sigma for all channels. NormalizeImageFilter#1

Already fixed or in progress

Related issues

Out of scope

  • No findings of this class are in filters with a V&V report (count 0).
  • Tuple-count checks (number of tuples vs number of geometry elements) are a different class.
  • Findings already fixed on develop or in an open PR (listed above).

Activity

  1. changed the title [-]BUG: Component shape not declared in 6 filters[/-] [+]BUG: C02: Component shape not declared in 6 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