From 7543d04fbe5044324cb87fd0e3c4ee2215271f55 Mon Sep 17 00:00:00 2001 From: Yarchik Date: Tue, 30 Jun 2026 14:31:16 +0100 Subject: [PATCH] fix: throw when encoding a BigInt outside int64/uint64 range With `useBigInt64: true`, `encodeBigInt64` branched only on sign and called the native `DataView.setBigUint64`/`setBigInt64`, which truncate mod 2^64 without throwing. A BigInt outside the representable range was silently corrupted on encode: encode(2n ** 64n, { useBigInt64: true }) // decoded as 0n encode(2n ** 64n + 1n, { useBigInt64: true }) // decoded as 1n encode(-(2n ** 63n) - 1n,{ useBigInt64: true }) // sign flip: 9223372036854775807n The MessagePack int family is fixed 64-bit, so the only representable range is the union of int64 and uint64: [-2^63, 2^64 - 1]. Add a range check that throws for anything outside it, matching how the encoder already rejects other unrepresentable inputs (too-long strings, too-large arrays/maps/binaries). --- src/Encoder.ts | 3 +++ test/bigint64.test.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/Encoder.ts b/src/Encoder.ts index b047c1d..0ac65f8 100644 --- a/src/Encoder.ts +++ b/src/Encoder.ts @@ -291,6 +291,9 @@ export class Encoder { } private encodeBigInt64(object: bigint): void { + if (object < -(BigInt(2) ** BigInt(63)) || object > BigInt(2) ** BigInt(64) - BigInt(1)) { + throw new Error(`Cannot encode BigInt as int64/uint64 because it is out of range: ${object}`); + } if (object >= BigInt(0)) { // uint 64 this.writeU8(0xcf); diff --git a/test/bigint64.test.ts b/test/bigint64.test.ts index e2bf08f..f6f5c96 100644 --- a/test/bigint64.test.ts +++ b/test/bigint64.test.ts @@ -35,4 +35,30 @@ describe("useBigInt64: true", () => { const encoded = encode(value, { useBigInt64: true }); assert.deepStrictEqual(decode(encoded, { useBigInt64: true }), value); }); + + it("round-trips the boundary values of int64/uint64", () => { + const values = [ + BigInt(0), + BigInt(42), + BigInt(2) ** BigInt(63) - BigInt(1), // max int64 + -(BigInt(2) ** BigInt(63)), // min int64 + BigInt(2) ** BigInt(64) - BigInt(1), // max uint64 + ]; + for (const value of values) { + const encoded = encode(value, { useBigInt64: true }); + assert.deepStrictEqual(decode(encoded, { useBigInt64: true }), value); + } + }); + + it("throws when a bigint is out of the int64/uint64 range", () => { + const values = [ + BigInt(2) ** BigInt(64), // uint64 max + 1 + BigInt(2) ** BigInt(64) + BigInt(1), + -(BigInt(2) ** BigInt(63)) - BigInt(1), // int64 min - 1 + -(BigInt(2) ** BigInt(100)), + ]; + for (const value of values) { + assert.throws(() => encode(value, { useBigInt64: true }), /out of range/); + } + }); });