Use long for hashCode in ReferentialComparer to avoid overflow by dennisdoomen · Pull Request #3204 · fluentassertions/fluentassertions · GitHub
Skip to content

Use long for hashCode in ReferentialComparer to avoid overflow#3204

Merged
dennisdoomen merged 1 commit into
mainfrom
fix/qodana-referential-comparer-overflow
May 5, 2026
Merged

Use long for hashCode in ReferentialComparer to avoid overflow#3204
dennisdoomen merged 1 commit into
mainfrom
fix/qodana-referential-comparer-overflow

Conversation

@dennisdoomen

Copy link
Copy Markdown
Member

Problem

The Qodana workflow on main has been failing since bumping JetBrains/qodana-action from 2025.3 to 2026.1 (run #25039911647). The new Qodana version introduced an improved Possible overflow in 'unchecked' context (High severity) inspection.

Root Cause

ReferentialComparer.GetHashCode used an int hashCode accumulator with hashCode * 397 multiplications that can silently overflow. The equivalent pattern in Node.cs already wraps the computation in unchecked {}, but ReferentialComparer.cs did not.

Fix

Changed the hashCode local variable from int to long so the multiplications cannot overflow, then narrowed back to int on return via an explicit truncating cast (the correct and expected behavior for hash codes).

HashCode.Combine is not an option since the project targets net47 and netstandard2.0.

Copilot AI review requested due to automatic review settings May 5, 2026 13:58
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dennisdoomen dennisdoomen force-pushed the fix/qodana-referential-comparer-overflow branch from ce6a88c to bb72faf Compare May 5, 2026 14:00
@dennisdoomen dennisdoomen changed the title fix: use long for hashCode in ReferentialComparer to avoid overflow Use long for hashCode in ReferentialComparer to avoid overflow May 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses a new Qodana (2026.1) high-severity inspection by preventing potential overflow in ReferentialComparer.GetHashCode, which is used by the equivalency engine to key tuple-based caches by object identity and index.

Changes:

  • Switched the hash accumulator from int to long to avoid overflow during hashCode * 397 multiplications.
  • Narrowed back to int at return via an explicit cast to preserve the required int hash code API surface.

@coveralls

coveralls commented May 5, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

Test Results

    37 files  ±0      37 suites  ±0   2m 46s ⏱️ +4s
 6 363 tests ±0   6 362 ✅ ±0  1 💤 ±0  0 ❌ ±0 
39 524 runs  ±0  39 518 ✅ ±0  6 💤 ±0  0 ❌ ±0 

Results for commit bb72faf. ± Comparison against base commit 84b7cb6.

This pull request removes 10 and adds 8 tests. Note that renamed tests count towards both.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HaveLength ‑ When_a_throwing_stream_should_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HaveLength ‑ When_a_throwing_stream_should_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HavePosition ‑ When_a_throwing_stream_should_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HavePosition ‑ When_a_throwing_stream_should_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHaveLength ‑ When_a_throwing_stream_should_not_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHaveLength ‑ When_a_throwing_stream_should_not_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHavePosition ‑ When_a_throwing_stream_should_not_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHavePosition ‑ When_a_throwing_stream_should_not_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GetLengthExceptionMessage'.)
Object name: 'GetPositionExceptionMessage'.)
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HaveLength ‑ When_a_throwing_stream_should_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GetLengthExceptionMessage'.)
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HaveLength ‑ When_a_throwing_stream_should_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GetLengthExceptionMessage'.)
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HavePosition ‑ When_a_throwing_stream_should_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GetPositionExceptionMessage'.)
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HavePosition ‑ When_a_throwing_stream_should_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GetPositionExceptionMessage'.)
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHaveLength ‑ When_a_throwing_stream_should_not_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GetLengthExceptionMessage'.)
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHaveLength ‑ When_a_throwing_stream_should_not_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GetLengthExceptionMessage'.)
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHavePosition ‑ When_a_throwing_stream_should_not_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GetPositionExceptionMessage'.)
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHavePosition ‑ When_a_throwing_stream_should_not_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GetPositionExceptionMessage'.)

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

@dennisdoomen dennisdoomen merged commit f81cb7c into main May 5, 2026
17 of 18 checks passed
@dennisdoomen dennisdoomen deleted the fix/qodana-referential-comparer-overflow branch May 5, 2026 14:30
searledan added a commit to fiscaltec/vitally-mcp that referenced this pull request May 12, 2026
Updated
[FluentAssertions](https://github.com/fluentassertions/fluentassertions)
from 8.9.0 to 8.10.0.

<details>
<summary>Release notes</summary>

_Sourced from [FluentAssertions's
releases](https://github.com/fluentassertions/fluentassertions/releases)._

## 8.10.0

<!-- Release notes generated using configuration in .github/release.yml
at main -->

## What's Changed
### Improvements
* Fail with a descriptive error when path-based rules are used on
value-semantic types by @​dennisdoomen in
fluentassertions/fluentassertions#3187
* Significantly speed up BeEquivalentTo for large unordered collections
by @​dennisdoomen in
fluentassertions/fluentassertions#3188
* Add ComparingNullCollectionsAsEmpty and ComparingNullStringsAsEmpty
options to BeEquivalentTo by @​dennisdoomen in
fluentassertions/fluentassertions#3202
* Include original index in extraneous item failure messages by
@​dennisdoomen in
fluentassertions/fluentassertions#3203
### Documentation
* Reroute the docs link to Xceed by @​dennisdoomen in
fluentassertions/fluentassertions#3183
* Fix typo in release notes by @​jnyrup in
fluentassertions/fluentassertions#3194
* Fix typos in docs by @​jnyrup in
fluentassertions/fluentassertions#3197
### Others
* Bump flatted from 3.4.1 to 3.4.2 in the npm_and_yarn group across 1
directory by @​dependabot[bot] in
fluentassertions/fluentassertions#3184
* Add AI assistant instruction file (agents.md) for Copilot, Claude, and
JetBrains Junie by @​Copilot in
fluentassertions/fluentassertions#3176
* Bump smol-toml from 1.6.0 to 1.6.1 in the npm_and_yarn group across 1
directory by @​dependabot[bot] in
fluentassertions/fluentassertions#3185
* Bump the npm_and_yarn group across 1 directory with 2 updates by
@​dependabot[bot] in
fluentassertions/fluentassertions#3186
* Bump cspell from 9.7.0 to 10.0.0 by @​dependabot[bot] in
fluentassertions/fluentassertions#3189
* Update nugets by @​jnyrup in
fluentassertions/fluentassertions#3192
* Fixup Qodana issues by @​jnyrup in
fluentassertions/fluentassertions#3193
* Fix Qodana argument separator by @​jnyrup in
fluentassertions/fluentassertions#3195
* Use new Qodana linter option by @​jnyrup in
fluentassertions/fluentassertions#3196
* Fix flaky BeLessThanOrEqualTo execution time test by @​Copilot in
fluentassertions/fluentassertions#3200
* Bump JetBrains/qodana-action from 2025.3 to 2026.1 by
@​dependabot[bot] in
fluentassertions/fluentassertions#3201
* Use long for hashCode in ReferentialComparer to avoid overflow by
@​dennisdoomen in
fluentassertions/fluentassertions#3204


**Full Changelog**:
fluentassertions/fluentassertions@8.9.0...8.10.0

Commits viewable in [compare
view](fluentassertions/fluentassertions@8.9.0...8.10.0).
</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dan Searle <dan_searle@outlook.com>
This was referenced Jun 29, 2026
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.

3 participants