Skip to content

fix: bound the Duplicate Files minimum-size box so it cannot invert its filter - #1926

Merged
laurentiu021 merged 1 commit into
mainfrom
fix/duplicate-finder-min-size-overflow
Aug 18, 2026
Merged

fix: bound the Duplicate Files minimum-size box so it cannot invert its filter#1926
laurentiu021 merged 1 commit into
mainfrom
fix/duplicate-finder-min-size-overflow

Conversation

@laurentiu021

Copy link
Copy Markdown
Owner

What a user sees

Open Duplicate Files, type a stray - (or a very large number) into "minimum size (KB)", and
Scan. Instead of finding only large duplicates it grinds through — and hashes — every file in the
folder. The one control meant to make the scan cheaper makes it maximally expensive, silently.

The defect

The scan keeps a file when it is at least the requested size:

if (fi.Length < minSizeBytes) continue;      // DuplicateFileService.Scan

and minSizeBytes was MinSizeKb * 1024, straight from a long-bound TextBox with no limits of its
own. Two typed values make that product negative:

  • a leading minus — -1-1024;
  • anything above long.MaxValue / 1024 — the multiply overflows and wraps negative.

Every real file length is ≥ 0 > negative, so the skip never fires: "only files above X" becomes
"every file in the folder". Nothing throws; the scan just does the opposite of what was asked, slowly.

The fix

The KB→bytes conversion moves into a pure, bounded helper:

internal const long MaxMinSizeKb = long.MaxValue / 1024;
internal static long MinBytesFor(long minSizeKb) => Math.Clamp(minSizeKb, 0, MaxMinSizeKb) * 1024;

so the threshold can never be negative by either route.

Deliberately not clamped in the setter. The box uses UpdateSourceTrigger=PropertyChanged, so
rewriting the value on every keystroke would fight the caret (type -, then a digit, and the field
would jump out from under you). The bound belongs where the value is used, not where it is typed —
the same reasoning the codebase already applies to live-updating inputs.

Found while triaging the pure-echo test MinSizeKb_CanBeChanged in batch 94, whose only "assertion"
set the property and read it back — it could never have caught this.

Verification

Red proof: 4 mutations, the touched source file restored byte-for-byte.

Mutation Result
drop the clamp entirely (the shipped code) negative and overflow tests both red
keep the lower bound only (Math.Max) overflow test red, negative test green — isolates the upper bound
keep the upper bound only (Math.Min) negative test red, overflow test green — isolates the lower bound
push MaxMinSizeKb one past the real limit fails to compile (CS0220): the test asserts against the constant expression MaxMinSizeKb * 1024, which overflows at compile time — so the boundary itself cannot drift silently

That last one is a stronger guard than a runtime assertion: an off-by-one in the limit is rejected by
the compiler, not merely by a failing test. The three runtime tests are [Theory] rows covering
ordinary values (unchanged behaviour), negatives (-1, -5000, long.MinValue), and the exact
overflow boundary.

Other checks: all four projects build 0 errors / 0 warnings; dotnet format --verify-no-changes exit 0
on both; author headers intact; leak scan over all 32 terms gives 0 hits. fix: → patch release
(1.65.19).

…ts filter

The scan keeps a file when `fi.Length >= minSizeBytes`, where minSizeBytes is the
user's "minimum size (KB)" figure times 1024. The TextBox binds a long and imposes
no bounds, so two typed values produced a NEGATIVE threshold:

  * a stray leading minus, and
  * any value above long.MaxValue / 1024, which overflows when scaled.

Every file is larger than a negative size, so `fi.Length < minSizeBytes` skipped
nothing: "only files above X" silently became "scan and hash every file in the
folder" — the opposite of the request, and much slower.

The KB→bytes conversion moved into a pure MinBytesFor(long) that clamps to
[0, MaxMinSizeKb] before scaling, so the threshold can never go negative by either
route. Not clamped in the setter on purpose: the box uses
UpdateSourceTrigger=PropertyChanged, so rewriting on every keystroke would fight
the caret; the bound belongs where the value is used.

Found while triaging the pure-echo test MinSizeKb_CanBeChanged (batch 94), whose
only "assertion" was a setter round-trip — it could never have caught this.

Red proof: 4 mutations. Dropping the clamp reddens both the negative and the
overflow test; a lower-bound-only and an upper-bound-only variant each redden
exactly one, proving neither half is redundant; and pushing MaxMinSizeKb one past
the real limit fails to COMPILE (CS0220 on the test's constant expression), so the
boundary itself cannot drift silently.
@laurentiu021
laurentiu021 merged commit abef9d4 into main Aug 18, 2026
5 checks passed
@laurentiu021
laurentiu021 deleted the fix/duplicate-finder-min-size-overflow branch August 18, 2026 15:34
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.

1 participant