Skip to content

Fix #360: validate tensor shapes at construction - #438

Merged
petlenz merged 2 commits into
mainfrom
fix-360-tensor-shape-validation
Sep 16, 2026
Merged

petlenz merged 2 commits into
mainfrom
fix-360-tensor-shape-validation

Conversation

@petlenz

@petlenz petlenz commented Sep 16, 2026 •

Copy link
Copy Markdown
Member

Closes #360.

Shape errors used to construct silently and surface later as out-of-bounds reads or garbage sums in the evaluator. All checks now throw invalid_expression_error (never assert), so they fire in Release too.

New shared header tensor/tensor_shape_validation.h; checks run in the factories before the zero short-circuits and in the node constructors, so rebuild/substitution paths are covered as well.

Operation Now rejected
tensor + / - dim or rank mismatch (including a tensor_zero of another shape, and n-ary chains like (X+Y)+C)
tensor * dim mismatch (same class, found while auditing)
inner_product (+ inner_product_wrapper ctor) dim mismatch; differing index counts; index out of range; duplicate index. The commented-out dim check is replaced by a real one
otimes / otimesu / otimesl (+ outer_product_wrapper ctor) dim mismatch; index counts ≠ operand ranks; result positions not a permutation
dot_product (+ tensor_inner_product_to_scalar ctor) assert(... || ...) → thrown full-contraction check: every index of both operands consumed (a scalar result requires &&), dims equal, indices valid
dot(A) rank ≠ 2 — the self-contraction evaluator (dcontract_self_op) is rank-2 only

Changed existing test (separate commit)

RoundTwoReview.DimChangingSubstitutionDropsTag → DimChangingSubstitutionThrows (tests/CoreBugFixTest.h).

Kept in its own commit so it can be reviewed or reverted independently of the fix.

Tests

6 new TensorShapeValidation.* tests in tests/TensorExpressionTest.h, covering each rejection above plus zero-operand short-circuits, direct make_expression construction, and cases that must still be accepted (X+Y, C:{3,4}X:{1,2}, otimesu(X,X), dot_product(X,{1,2},Y,{2,1}), dot(X)).

Negative control: against unfixed code every rejection assertion fails (no crash); with the fix all pass.

Suite: Debug (gcc-14, -Werror minus the pre-existing pedantic/deprecated-declarations warnings) 2352/2352; Release 2352/2352 — the point of the issue is that validation is not assert-only. Fuzz binary unchanged (13 known rank-limit skips). clang-format-18 clean.

Review note — coverage deliberately dropped. The replaced test also pinned a round-3 finding ("evaluation must throw rather than over-read the buffer"). That assertion is unreachable now: the malformed node cannot be built by any path, since both the factory and the inner_product_wrapper constructor validate (verified). Recorded here so the lost assertion is a decision, not an omission.

Parser interaction: shape errors now escape parse() as cas_error without position info (e.g. parse("A + W"), parse("dot(u)")). Noted on #282, which tracks position threading for inside-dispatch errors.

Shape errors used to build silently and surface as out-of-bounds reads in
the evaluator. Tensor +/-/* , inner_product, otimes/otimesu/otimesl,
dot_product and dot now throw invalid_expression_error (not assert) in
the factories, before the zero short-circuits, and in the node
constructors so rebuild/substitution paths are covered too.

Signed-off-by: petlenz <peterlenz89.pl@gmail.com>
…-time rejection

substitute(sym(A), A, E) with mismatched dims now throws
invalid_expression_error while rebuilding the contraction, so the
tag/evaluation assertions it made are unreachable.

Signed-off-by: petlenz <peterlenz89.pl@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No dim/rank validation on tensor add / inner_product construction; dot_product precondition uses || and is assert-only

1 participant