Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
- vNext
- Fix building with Clang 22+ by making bigint serialization byte-oriented instead of relying on mismatched `uint64_t`/`unsigned long` pointer types
- Fix Ruby bigint serialization to consider all packed limbs instead of truncating values above 512 bits

- 0.21.4 - 24-06-2026
- Fix stale V8 termination state after interrupts/timeouts so contexts remain usable after cancelled evaluations
- Let Ruby interrupts wake MiniRacer calls without immediately terminating V8, allowing signal traps and nested callbacks to unwind safely
Expand Down
2 changes: 1 addition & 1 deletion ext/mini_racer_extension/mini_racer_extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ static int serialize1(Ser *s, VALUE refs, VALUE v)
if (sign < 0)
v = rb_big_mul(v, LONG2FIX(-1));
rb_big_pack(v, limbs, countof(limbs));
ser_bigint(s, limbs, countof(limbs), sign);
ser_bigint(s, limbs, sizeof(limbs), sign);
break;
case T_FIXNUM:
ser_int(s, FIX2LONG(v));
Expand Down
22 changes: 13 additions & 9 deletions ext/mini_racer_extension/serde.c
Original file line number Diff line number Diff line change
Expand Up @@ -243,27 +243,31 @@ static void ser_num(Ser *s, double v)
}
}

// ser_bigint: |n| is in bytes, not quadwords
static void ser_bigint(Ser *s, const uint64_t *p, size_t n, int sign)
// ser_bigint: |p| points to |n| bytes, interpreted as little-endian
// 64-bit words. The buffer may be backed by Ruby's unsigned long limbs or
// V8-style uint64_t words; keep the interface byte-oriented so callers don't
// need to agree on the concrete typedef used for a 64-bit word.
static void ser_bigint(Ser *s, const void *p, size_t n, int sign)
{
const uint8_t *bytes;

if (*s->err)
return;
if (n % 8) {
snprintf(s->err, sizeof(s->err), "bad bigint");
return;
}
bytes = p;
w_byte(s, 'Z');
// chop off high all-zero words
n /= 8;
while (n--)
if (p[n])
break;
if (n == (size_t)-1) {
while (n > 0 && bytes[n-1] == 0)
n--;
if (n == 0) {
w_byte(s, 0); // normalized zero
} else {
n = 8*n + 8;
n = (n + 7) & ~(size_t)7;
w_varint(s, 2*n + (sign < 0));
w(s, p, n);
w(s, bytes, n);
}
}

Expand Down
22 changes: 22 additions & 0 deletions test/mini_racer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1628,6 +1628,28 @@ def test_large_integer
end
end

def test_large_bigint_serialization_uses_all_packed_limbs
if RUBY_ENGINE == "truffleruby"
skip "C extension is not used on TruffleRuby"
end

[
2**64,
-(2**64),
2**128 + 2**64 + 12_345,
-(2**128 + 2**64 + 12_345),
2**512,
-(2**512 + 1)
].each do |big_int|
context = MiniRacer::Context.new
context.attach("test", proc { big_int })

assert_equal "bigint", context.eval("typeof test()")
assert_equal big_int.to_s, context.eval("test().toString()")
assert_equal big_int, context.eval("test()")
end
end

def test_uint8array_is_converted_to_string
context = MiniRacer::Context.new
result = context.eval('new Uint8Array([0, 1, 2, 3])')
Expand Down
Loading