Release candid 0.10.28 and candid_parser 0.3.2#731
Merged
Conversation
…ndary The fast paths added in #721 for deserialize_nat/deserialize_int call hand-rolled LEB128 readers that bail to the bignum decoder on overflow. The overflow check ran after `shift += 7` and only fired at shift >= 70, but the in-loop write `result |= low << shift` already truncated high bits at shift = 63 — silently returning a wrong value instead of signalling overflow. e.g. `Nat::from(1u128 << 64)` round-tripped to 0. Lower the bailout threshold so the fast path only handles values encodable in 9 LEB128 bytes (fits in 63 bits). Anything that would require a 10th byte falls through to the existing bignum decoder, which handles arbitrary precision correctly. This also lets us drop the `if shift < 64` guard in the hot loop body and the analogous guard in the sign-extension step. Tradeoff: u64 values in [2^63, u64::MAX] and i64 values like i64::MIN now take the bignum path even though they fit. The fast path stays branch-light, and the affected range is well outside the common case (small counters, token amounts up to ~9.2 * 10^18). Includes a regression test reproducing the original bug report. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…kage The small-value fast path in `Int::decode` (#717) keeps the running value in an `i64` until it can prove a chunk doesn't fit in i64, at which point it lifts to a `BigInt`. At shift in [57, 64), `fits_i64` checks whether the chunk's non-data bits match the sign-extension pattern — but it did so without requiring the byte to be terminal. For an input like `i128::MAX` (LEB128: 18 × 0xff, then 0x01), iteration 10 has shift=63, byte=0xff (continuation set). The check looks at `low_bits | !0x7f == -1` and concludes "fits", then runs `small |= low_bits << 63`, which silently truncates bits 1..6 of `low_bits`. The corrupted `i64` (now -1) is then carried into the BigInt path as the seed, and the final decoded value is -1. Require `byte & 0x80 == 0` in the boundary branch so the check only declares "fits" on a terminal byte; otherwise the BigInt path takes over with the correct seed before any truncating shift happens. Includes a regression test covering i128::MAX/MIN, i128 mid-range, and beyond-i128 values for both signs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Click to see raw report |
There was a problem hiding this comment.
Pull request overview
This PR prepares the candid and candid_parser crates for release by fixing LEB128/SLEB128 decoding fast paths that could silently truncate large Nat/Int values near the 64-bit boundary, adding regression tests for those cases, and bumping crate versions + changelog accordingly.
Changes:
- Fix
DeserializerLEB128 fast paths to bail out to bignum decoding before any 64-bit truncation can occur. - Fix
Int::decodesmall-value fast path to only treat the terminal LEB128 byte as authoritative for “fits in i64”. - Add regression tests covering boundary/large-magnitude round-trips; bump versions and update changelog/lockfiles for the release.
Reviewed changes
Copilot reviewed 7 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| rust/candid/tests/serde.rs | Adds regression tests for Nat/Int round-trips across the fast-path boundary and large-magnitude Int::decode cases. |
| rust/candid/src/types/number.rs | Tightens Int::decode fast-path fits_i64 logic to avoid truncation when continuation bytes follow. |
| rust/candid/src/de.rs | Updates LEB128 fast-path readers to bail out earlier (before the 64-bit boundary) and clarifies behavior in docs. |
| rust/candid/Cargo.toml | Bumps candid to 0.10.28 and pins candid_derive to the matching version. |
| rust/candid_derive/Cargo.toml | Bumps candid_derive to 0.10.28. |
| rust/candid_parser/Cargo.toml | Bumps candid_parser to 0.3.2. |
| rust/bench/Cargo.lock | Updates bench lockfile versions to the new crate releases. |
| Cargo.lock | Updates workspace lockfile to candid 0.10.28 / candid_parser 0.3.2. |
| CHANGELOG.md | Adds a 2026-05-20 release entry describing the fixes and released versions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
raymondk
approved these changes
May 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Nat/Intvalues near theu64/i64boundary during decoding (ecc0c45)Int::decodetruncating large magnitudes due to fast-path leakage (d08b8da)candid0.10.27 → 0.10.28 andcandid_deriveto matchcandid_parser0.3.2, which moves the previously unreleased MotokoFloat32binding fix into a dated releaseCHANGELOG.mdwith a 2026-05-20 entry covering both cratesTest plan
cargo check -p candid -p candid_derive -p candid_parser🤖 Generated with Claude Code