Skip to content

Drop params from Mat/UMat ROI constructors; fix related silent-empty-array bugs - #2063

Merged
shimat merged 4 commits into
mainfrom
fix-mat-roi-params-2062
Jul 19, 2026
Merged

Drop params from Mat/UMat ROI constructors; fix related silent-empty-array bugs#2063
shimat merged 4 commits into
mainfrom
fix-mat-roi-params-2062

Conversation

@shimat

@shimat shimat commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • Mat(Mat m, params Range[] ranges) (and the same shape on UMat/Mat<TElem>) let a plain new Mat(m) - meant as a copy/view - compile fine, silently binding to the ROI constructor with an empty ranges array and throwing ArgumentException: empty ranges at runtime instead of failing to compile. Dropped params from the Mat/UMat/Mat<TElem> ROI constructors so callers must pass the array explicitly (e.g. new Mat(m, [r0, r1])).
  • Fixing the build surfaced a live instance of exactly this bug: MatMemoryManager's new Mat(mat) (the isDataOwner: false path) was silently resolving to the ROI constructor, because the constructor it actually meant to call, Mat(Mat m), was protected and inaccessible from there. Changed it to internal so the correct constructor binds.
  • Audited the rest of the params-array surface for the same shape (an empty array compiles fine but is invalid, unsafe, or just hard to read) and made the following changes:
    • Mat.SubMat() / UMat.SubMat() with zero ranges hit undefined behavior in the native glue code (&rangesVec[0] on an empty std::vector). Dropped params here too, so this is a compile error rather than a runtime guard. The two-argument Range call sites (e.g. mat.SubMat(0..2, 1..4)) are unaffected, since those resolve to the separate SubMat(System.Range, System.Range) overload.
    • Mat.Ptr() with too few indices let cv::Mat::ptr(const int*) read Dims many ints from a shorter native buffer - an out-of-bounds read. This one can't drop params the same way (the N-D Ptr(int[] idx) overload has legitimate 1/2/3/4+-argument uses that only work via params), so it's guarded instead, but DEBUG-only: Dims is itself a P/Invoke call, and Ptr sits on the per-pixel-access hot path, so this check (and its test) exist only in DEBUG builds, matching cv::Mat::ptr's own CV_DbgAssert-only bounds checking and keeping the Release fast path untouched.
    • SparseMat.Create had a pre-existing typo (sizes.Length == 1 instead of == 0) that rejected valid 1-D sparse mats while letting truly empty sizes through. Fixed, and the same guard was added to SparseMat<T>(params int[] dimensions).
    • FileNode/FileStorage.GetPath(params object[] path) already guarded path.Length == 0 at runtime with a clear message - the same "should have been a compile error" situation as SubMat. Dropped params here too.
    • Mat/UMat.Reshape(int cn, params int[] newDims): this one is a readability call rather than a safety one - a run of bare ints after cn reads as an undifferentiated list, whereas newDims: [5, 10, 15] visually groups them as one shape value. Reshape(int cn, int rows = 0) is untouched and still covers the common 0/1-argument case.
    • Mat.SetArray<T>(params T[] data): no bug either, but the only two call sites relying on the spread form were trivial to convert to an explicit array, so there was no reason to keep the params surface.
    • Cv2.ImWrite/ImEncode/Mat.ToBytes previously carried two overloads each: a raw int[]-pairs form (originally kept as an escape hatch for encoding flags not covered by ImageEncodingParam/ImwriteFlags) and a params ImageEncodingParam[] form. Since ImwriteFlags is a plain enum, any flag value - including ones without a named member - can already be expressed as (ImwriteFlags)rawValue, so the raw int[] form wasn't buying any coverage the typed form couldn't already reach. Removed the raw overloads and merged everything onto a single ImageEncodingParam[]? prms = null parameter per method (ToMemoryStream/WriteToStream already had no int[] sibling and gain the same default).
    • Reviewed and deliberately left Window.ShowImages, MatShape, and ImWrite/ImEncode/ToBytes's remaining ImageEncodingParam[] convenience alone: each has real call sites that rely on the bare-argument spread form (Window.ShowImages(a, b, c) across 30+ call sites; new MatShape(2, 3, 4) as the primary construction idiom with an explicitly-documented 0-argument scalar-shape meaning), and none has a live safety issue motivating the change.

Closes #2062

Testing

  • dotnet build OpenCvSharp.sln -c Debug / dotnet build test\OpenCvSharp.Tests\OpenCvSharp.Tests.csproj -c Release (the solution-wide Release build fails locally on the native OpenCvSharpExtern.vcxproj needing full MSBuild/VS C++ tooling, unrelated to this change; the C# projects build fine standalone in both configurations)
  • dotnet test test\OpenCvSharp.Tests\OpenCvSharp.Tests.csproj -c Debug --filter "FullyQualifiedName~OpenCvSharp.Tests.Core|FullyQualifiedName~OpenCvSharp.Tests.Imgcodecs" (503 passed, 3 skipped)
  • dotnet test test\OpenCvSharp.Tests\OpenCvSharp.Tests.csproj -c Release --filter "FullyQualifiedName~OpenCvSharp.Tests.Core|FullyQualifiedName~OpenCvSharp.Tests.Imgcodecs" (502 passed, 3 skipped - the DEBUG-only Ptr guard test is excluded from Release, matching CI's -c Release test runs)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • API Improvements
    • Standardized array-based arguments for matrix slicing, reshaping, data assignment, and file-node paths.
    • Simplified image encoding and writing options with nullable encoding-parameter arrays.
  • Bug Fixes
    • Added validation for empty ranges, missing dimensions, null indices, and insufficient pointer indices.
    • Corrected sparse matrix size validation.
  • Tests
    • Added coverage for memory sharing, invalid arguments, matrix subregions, sparse matrices, and image encoding behavior.

…array bugs

new Mat(m, params Range[] ranges) let a plain new Mat(m) - meant as a
copy/view - compile fine and bind to the ROI constructor with an empty
ranges array, throwing "empty ranges" at runtime instead of failing to
compile (#2062). Drop params from the Mat/UMat/Mat<TElem> ROI
constructors so callers must pass the array explicitly.

Fixing the build surfaced a live instance of exactly this bug:
MatMemoryManager's new Mat(mat) (isDataOwner: false path) was silently
resolving to the ROI constructor because the intended copy constructor,
Mat(Mat m), was protected and inaccessible from there. Changed it to
internal so the correct constructor binds.

Auditing the rest of the params-array surface for the same shape found
further cases where an empty array compiles but is either invalid or
unsafe:
- Mat.SubMat()/UMat.SubMat() with zero ranges hit undefined behavior in
  the native glue (&rangesVec[0] on an empty vector). Now guarded.
- Mat.Ptr() with too few indices let cv::Mat::ptr(const int*) read Dims
  many ints from a shorter native buffer - an out-of-bounds read. Guarded,
  but only in DEBUG: Dims is itself a P/Invoke call, and Ptr sits on the
  per-pixel-access hot path, so the check (and its test) only exist in
  DEBUG builds, matching cv::Mat::ptr's own CV_DbgAssert-only checking.
- SparseMat.Create had a pre-existing typo (sizes.Length == 1 instead of
  == 0) that rejected valid 1-D sparse mats while letting truly empty
  sizes through; fixed, and the same guard was added to the
  SparseMat<T>(params int[] dimensions) constructor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5518316f-340c-49df-855d-65aacbd25442

📥 Commits

Reviewing files that changed from the base of the PR and between 0350ba2 and 2bd2279.

📒 Files selected for processing (1)
  • src/OpenCvSharp/Modules/core/SparseMat.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/OpenCvSharp/Modules/core/SparseMat.cs

📝 Walkthrough

Walkthrough

Matrix ROI, reshape, pointer, and encoding APIs now require explicit arrays or typed parameters, with added argument validation. File path and SparseMat validation are updated, and tests cover these changes plus MatMemoryManager ownership behavior.

Changes

Matrix API contracts and validation

Layer / File(s) Summary
Matrix API contracts and guards
src/OpenCvSharp/Modules/core/Mat/*, src/OpenCvSharp/Modules/core/Mat/UMat.cs
ROI, reshape, array-setting, pointer, and stream-related APIs use explicit arrays or typed parameters; invalid ranges and insufficient pointer indices are rejected.
Matrix regression coverage
test/OpenCvSharp.Tests/core/MatTest.cs, test/OpenCvSharp.Tests/core/UMatTest.cs, test/OpenCvSharp.Tests/core/MatExprBehaviorTest.cs
Tests cover explicit ROI and reshape arrays, empty ranges, pointer validation, and updated array-setting calls.

SparseMat dimension validation

Layer / File(s) Summary
SparseMat dimension validation
src/OpenCvSharp/Modules/core/SparseMat.cs, test/OpenCvSharp.Tests/core/SparseMatTest.cs
Empty size checks are corrected and generic dimension validation is centralized, with valid and invalid creation tests.

Encoding and file-path signatures

Layer / File(s) Summary
Typed image encoding parameters
src/OpenCvSharp/Modules/core/Mat/Mat.cs, src/OpenCvSharp/Cv2/Cv2_imgcodecs.cs, test/OpenCvSharp.Tests/imgcodecs/ImgCodecsTest.cs
Encoding methods accept nullable ImageEncodingParam[] arrays and convert them for native calls.
Explicit file path arrays
src/OpenCvSharp/Modules/core/FileNode.cs, src/OpenCvSharp/Modules/core/FileStorage.cs, test/OpenCvSharp.Tests/core/FileStorageTest.cs
GetPath methods require explicit object[] path segments and tests use the revised signatures.

MatMemoryManager coverage

Layer / File(s) Summary
Owned and shared memory behavior
test/OpenCvSharp.Tests/core/MatMemoryManagerTest.cs
Tests verify owned spans expose source data and non-owned spans reflect later source mutations.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main API cleanup and empty-array bug fixes in the PR.
Linked Issues check ✅ Passed The ROI constructors were fixed as requested, and the related params-array audits and tests align with #2062.
Out of Scope Changes check ✅ Passed No clearly unrelated code changes stand out; the extra API adjustments are consistent with the params/empty-array cleanup theme.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-mat-roi-params-2062

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/OpenCvSharp/Modules/core/SparseMat.cs (1)

215-216: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Provide the parameter name to ArgumentException.

Consider adding nameof(sizes) to the ArgumentException to match the behavior in ValidateDimensions below and to improve error clarity.

♻️ Proposed refactor
-        if (sizes.Length == 0)
-            throw new ArgumentException("sizes is empty");
+        if (sizes.Length == 0)
+            throw new ArgumentException("sizes is empty", nameof(sizes));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/OpenCvSharp/Modules/core/SparseMat.cs` around lines 215 - 216, Update the
ArgumentException thrown by the sizes.Length validation in SparseMat to include
nameof(sizes) as its parameter name, matching the existing ValidateDimensions
convention while preserving the current message and validation behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/OpenCvSharp/Modules/core/SparseMat.cs`:
- Around line 215-216: Update the ArgumentException thrown by the sizes.Length
validation in SparseMat to include nameof(sizes) as its parameter name, matching
the existing ValidateDimensions convention while preserving the current message
and validation behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f3754dce-0fa3-4929-bc74-e4832c930702

📥 Commits

Reviewing files that changed from the base of the PR and between dd990cd and f2ef951.

📒 Files selected for processing (8)
  • src/OpenCvSharp/Modules/core/Mat/Mat.cs
  • src/OpenCvSharp/Modules/core/Mat/MatOfT.cs
  • src/OpenCvSharp/Modules/core/Mat/UMat.cs
  • src/OpenCvSharp/Modules/core/SparseMat.cs
  • test/OpenCvSharp.Tests/core/MatMemoryManagerTest.cs
  • test/OpenCvSharp.Tests/core/MatTest.cs
  • test/OpenCvSharp.Tests/core/SparseMatTest.cs
  • test/OpenCvSharp.Tests/core/UMatTest.cs

shimat and others added 2 commits July 19, 2026 15:18
Same rationale as the ROI constructors: mat.SubMat() with zero ranges
should fail to compile, not throw (or previously, hit undefined
behavior in the native glue). This is strictly better than the runtime
guard added earlier - no runtime cost at all, in any configuration -
and needs no companion analyzer rule.

The two/three-argument Range call sites are unaffected: Mat/UMat also
expose a dedicated SubMat(System.Range, System.Range) overload that
mat.SubMat(0..2, 1..4)-style calls already resolved to, independent of
the params array overload. Only the genuinely N-ary (3+) explicit-array
call and the empty-array defense-in-depth tests needed updating.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…/ToBytes onto ImageEncodingParam[]

Continues the same-shaped cleanup from the previous commits, applied to
overloads that were reviewed and found safe to leave alone at the time,
but turned out to have no real ergonomic reason to keep params either:

- FileNode/FileStorage.GetPath(params object[] path): already guarded
  path.Length == 0 at runtime with a clear message - the same "should
  have been a compile error" situation as SubMat. No competing overload
  exists at the same arity, so there is no ambiguity risk either.
- Mat/UMat.Reshape(int cn, params int[] newDims): dropping params here
  is a readability call, not a safety one. A run of bare ints after cn
  reads as an undifferentiated list ("what do these numbers mean?"),
  whereas newDims: [5, 10, 15] visually groups them as one shape value.
  Reshape(int cn, int rows = 0) is untouched and still covers the
  common 0/1-argument case in normal form.
- Mat.SetArray<T>(params T[] data): no bug either, but the only two
  call sites that relied on the spread form were trivial to convert to
  an explicit array, so there was no reason to keep the params surface.

Cv2.ImWrite/ImEncode/Mat.ToBytes previously carried two overloads each:
a raw int[]-pairs form (kept as an escape hatch for encoding flags not
covered by the ImageEncodingParam/ImwriteFlags enum) and a
params ImageEncodingParam[] form. Since ImwriteFlags is a plain enum,
any flag value - including ones without a named member - can already
be expressed as (ImwriteFlags)rawValue, so the raw int[] form was not
actually buying any coverage the typed form couldn't already reach.
Removed the raw overloads and merged everything onto a single
ImageEncodingParam[]? prms = null parameter per method (ToMemoryStream/
WriteToStream already had no int[] sibling and gain the same default).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@shimat shimat self-assigned this Jul 19, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/OpenCvSharp/Cv2/Cv2_imgcodecs.cs (1)

285-286: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer BCL argument-validation helpers for string checks.

The manual string.IsNullOrEmpty checks violate the coding guidelines, which require using BCL argument-validation helpers instead of manual checks. You can use .NET 7+ ArgumentException.ThrowIfNullOrEmpty to simplify these guards natively.

  • src/OpenCvSharp/Cv2/Cv2_imgcodecs.cs#L285-L286: Replace the manual string.IsNullOrEmpty check with ArgumentException.ThrowIfNullOrEmpty(fileName);.
  • src/OpenCvSharp/Cv2/Cv2_imgcodecs.cs#L306-L307: Replace the manual string.IsNullOrEmpty check with ArgumentException.ThrowIfNullOrEmpty(fileName);.
  • src/OpenCvSharp/Cv2/Cv2_imgcodecs.cs#L450-L451: Replace the manual string.IsNullOrEmpty check with ArgumentException.ThrowIfNullOrEmpty(ext);.
♻️ Proposed refactor
-        if (string.IsNullOrEmpty(fileName))
-            throw new ArgumentNullException(nameof(fileName));
+        ArgumentException.ThrowIfNullOrEmpty(fileName);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/OpenCvSharp/Cv2/Cv2_imgcodecs.cs` around lines 285 - 286, Replace the
manual string.IsNullOrEmpty guards with ArgumentException.ThrowIfNullOrEmpty in
Cv2_imgcodecs.cs at lines 285-286 and 306-307 for fileName, and lines 450-451
for ext. Preserve the existing argument-validation behavior while using the BCL
helper.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/OpenCvSharp/Cv2/Cv2_imgcodecs.cs`:
- Around line 285-286: Replace the manual string.IsNullOrEmpty guards with
ArgumentException.ThrowIfNullOrEmpty in Cv2_imgcodecs.cs at lines 285-286 and
306-307 for fileName, and lines 450-451 for ext. Preserve the existing
argument-validation behavior while using the BCL helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: acf82797-23f0-4e40-98e8-a94ee26d349e

📥 Commits

Reviewing files that changed from the base of the PR and between a09da5a and 0350ba2.

📒 Files selected for processing (10)
  • src/OpenCvSharp/Cv2/Cv2_imgcodecs.cs
  • src/OpenCvSharp/Modules/core/FileNode.cs
  • src/OpenCvSharp/Modules/core/FileStorage.cs
  • src/OpenCvSharp/Modules/core/Mat/Mat.cs
  • src/OpenCvSharp/Modules/core/Mat/MatOfT.cs
  • src/OpenCvSharp/Modules/core/Mat/UMat.cs
  • test/OpenCvSharp.Tests/core/FileStorageTest.cs
  • test/OpenCvSharp.Tests/core/MatExprBehaviorTest.cs
  • test/OpenCvSharp.Tests/core/MatTest.cs
  • test/OpenCvSharp.Tests/imgcodecs/ImgCodecsTest.cs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/OpenCvSharp/Modules/core/Mat/MatOfT.cs
  • test/OpenCvSharp.Tests/core/MatTest.cs

Matches the ValidateDimensions convention added alongside it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@shimat
shimat merged commit 57a457e into main Jul 19, 2026
14 checks passed
@shimat
shimat deleted the fix-mat-roi-params-2062 branch July 19, 2026 10:05
@shimat shimat added the bug Confirmed defect in OpenCvSharp label Jul 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Confirmed defect in OpenCvSharp

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mat(Mat, params Range[]) / UMat(UMat, params Range[]): params turns an accidental 1-arg call into a runtime exception instead of a compile error

1 participant