Skip to content

fix(typeddata): panic encoding 64-bit values above ~2^53 (#10) - #29

Open
gfeyer wants to merge 3 commits into
negasus:masterfrom
gfeyer:issue_10
Open

gfeyer wants to merge 3 commits into
negasus:masterfrom
gfeyer:issue_10

Conversation

@gfeyer

@gfeyer gfeyer commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Closes #10.

typeddata.Encode allocates an 8-byte buffer for varint encoding, but the SPOP varint format needs up to 10 bytes for the full uint64 range. When the buffer overflows, varint.PutUvarint returns -1, the next line evaluates b[:-1], and the worker goroutine panics with slice bounds out of range [:-1]. Easiest real-world trigger is a handler that does SetVar(..., time.Now().UnixNano()).

Less obvious: negative int32 values also panic, because uint64(int32(-1)) sign-extends to MaxUint64 and needs the full 10-byte encoding too.

Fix: right-size the buffer per case rather than bumping everything uniformly, to keep allocations minimal on the hot encode path:

Case Buffer Reasoning
int32 10 Negative values sign-extend to ~MaxUint64
uint32 8 Max 2³²-1 fits in 5 bytes, always non-negative
int / int64 / uint / uint64 10 Full 64-bit range
string / []byte length prefix 8 len(v) cannot realistically reach 2⁵³ (9 PB)

gfeyer added 3 commits June 6, 2026 11:59
 SPOP peer varint encoding needs up to 10 bytes for the full uint64 range (4 bits in byte 0 + 7 bits per following byte = 67 bits at 10 bytes). The 8-byte buffer used in Encode caps out at ~53 bits — any value above that makes PutUvarint return -1 and then b[:i] panics with "slice bounds out of range [:-1]". A current-day time.Now().UnixNano() trips it immediately.
Unify all eight call sites in Encode to 10 bytes: numeric types (int32, uint32, int, int64, uint, uint64) and length prefixes for string and []byte. The package's own varint tests already use a 10-byte buffer, so this just makes Encode match.
Regression tests from the previous commit now pass.
The previous fix bumped all eight call sites uniformly to 10 bytes, but
only some need it. Right-size each case to keep allocations minimal on
the hot encode path:

  int32, int, int64, uint, uint64  → 10 bytes (full uint64 range)
  uint32                           →  8 bytes (max ~4.3e9 fits in 5)
  string / []byte length prefix    →  8 bytes (len(v) never reaches 2^53)

Subtle case worth noting: int32 needs 10 bytes too, but not for the
obvious reason. Negative int32 values sign-extend to ~MaxUint64 when
widened via uint64(v), so they hit the same overflow path as int64.
Added a code comment on that line and a TestEncode_Int32_Negative
regression test (round-trips int32(-1) — fails on the original 8-byte
buffer with the same slice-bounds panic).
@gfeyer

gfeyer commented Jun 13, 2026

Copy link
Copy Markdown
Contributor Author

Hi @negasus ! Can I please get a review/approval/merge for this bug fix? Thank you!

@gfeyer

gfeyer commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Friendly bump!

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.

Bug with 64-bit varint encodings

1 participant