Fix BigInt::div bugs and preclude aliasing via its interface#9121
Fix BigInt::div bugs and preclude aliasing via its interface#9121tautschnig wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes a long-standing bug in the BigInt::div combined quotient/remainder implementation where (in the single-digit-divisor fast path) the quotient buffer was mistakenly initialised from the divisor instead of the dividend. This aligns the BigInt::div behaviour with the rest of the big-int division code paths (where the dividend’s digit array is the input that gets transformed into the quotient).
Changes:
- Fix
BigInt::divsingle-digit-divisor branch to initialiseqfromx(dividend) sodigit_divproduces the correct quotient. - Add a unit test that exercises the previously-uncovered case: dividend > 64 bits with a single-digit divisor, for both positive and negative dividends.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| unit/big-int/big-int.cpp | Adds a regression unit test covering the >64-bit dividend + single-digit divisor BigInt::div path (including negative dividend semantics). |
| src/big-int/bigint.cc | Fixes quotient initialisation in BigInt::div so the single-digit-divisor fast path computes q from the dividend as intended. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #9121 +/- ##
===========================================
+ Coverage 80.77% 80.83% +0.05%
===========================================
Files 1712 1715 +3
Lines 189833 189982 +149
Branches 73 73
===========================================
+ Hits 153347 153576 +229
+ Misses 36486 36406 -80 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
e0fcf94 to
af130e2
Compare
In the single-digit-divisor branch of the combined quotient/remainder method BigInt::div, the quotient was initialized from the divisor (q = y) instead of the dividend (q = x). As digit_div transforms the dividend in place into the quotient, any division of a dividend exceeding 64 bits by a divisor fitting a single 32-bit digit returned q = 1, r = 0 (the divisor divided by itself). The bug dates back to the initial import of the big-int library. It went unnoticed because the only in-tree caller of BigInt::div is modinv, which itself has no callers, and the number.tst data driving the existing unit test never combines a dividend of more than 64 bits with a single-digit divisor. Add unit tests covering exactly that combination, for positive and negative dividends and divisors as well as a zero remainder, and also covering the long-division branch of BigInt::div, which was previously never exercised directly either. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
af130e2 to
9dd93d1
Compare
The general-division branch of BigInt::div set the output operands' lengths before resizing them. BigInt::resize copies `length` digits from the previous buffer into the newly allocated one, so whenever the quotient (or remainder) needed more digits than the output operand's current buffer held, resize read past the end of that buffer. The copied garbage was subsequently overwritten, leaving results correct, but the read is undefined behavior and is flagged by address sanitizer. Resize first and set the length afterwards, so that resize only copies the digits that are actually valid. The existing tests mask this because the number.tst-driven loop reuses output operands whose buffers have grown large in earlier iterations; the next commit, which makes div return freshly constructed values, would expose the over-read in every address sanitizer run. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
BigInt::div wrote into caller-provided output arguments before it was done reading the inputs: div(x, y, y, r) clobbered the divisor before its first digit was read and silently computed garbage, and passing the same object for quotient and remainder cannot produce a valid result at all. Rather than documenting the restriction (which leaves the breakage silent) or detecting aliased arguments at runtime, change the interface so that the aliasing cannot be expressed in the first place: div now returns a divisiont struct holding freshly constructed quotient and remainder values. Named return value optimization applies, so the non-aliased case does not pay for a copy. On the division-by-zero error path the returned values are now deterministically zero, where previously the output arguments were left untouched after printing the error. Adjust the only in-tree caller, modinv, and the unit tests, which as a side effect no longer need pre-declared output variables. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
9dd93d1 to
5bfb655
Compare
This PR fixes two long-standing bugs in the combined quotient/remainder method
BigInt::divand then changes its interface so that argument aliasing — the source of one of the defect classes — becomes impossible to express.Fix BigInt::div computing quotient from divisor instead of dividend. In the single-digit-divisor branch, the quotient was initialized from the divisor (
q = y) instead of the dividend (q = x). Asdigit_divtransforms the dividend in place into the quotient, any division of a dividend exceeding 64 bits by a divisor fitting a single 32-bit digit returned q = 1, r = 0. The bug dates back to the initial import; it went unnoticed because the only in-tree caller ofBigInt::divismodinv, which itself has no callers, and the number.tst data never combines a >64-bit dividend with a single-digit divisor. Unit tests now cover that combination for all four sign combinations, a zero remainder, and the long-division branch.Fix out-of-bounds read in BigInt::div when growing output operands. The general-division branch set the outputs' lengths before resizing them;
resizecopieslengthdigits from the previous buffer, reading past its end whenever the result needed more digits than the output held. Results were unaffected, but the read is undefined behavior and flagged by address sanitizer. The existing tests masked this only because the test loop reuses output operands with previously grown buffers; the interface change in the next commit would expose it in every sanitizer run.Make BigInt::div return its results, precluding aliasing.
divwrote into caller-provided output arguments before it was done reading the inputs, sodiv(x, y, y, r)silently computed garbage, and passing the same object for quotient and remainder cannot have a valid result at all. Per our policy that aliasing must never be supported and should be excluded at compile time where possible,divnow returns aBigInt::divisiontstruct holding freshly constructed quotient and remainder — the aliasing cannot be written anymore. NRVO applies, so the non-aliased case pays nothing. The only in-tree caller (modinv) and the unit tests are updated.