From c255f062c43197e59fe7042d353e84efc1344c30 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Wed, 24 Jun 2026 15:32:59 +1000 Subject: [PATCH] FIX: support clang 22+ compilation stop relying on mismatched uint64_t / unsigned long --- CHANGELOG | 4 ++++ .../mini_racer_extension.c | 2 +- ext/mini_racer_extension/serde.c | 22 +++++++++++-------- test/mini_racer_test.rb | 22 +++++++++++++++++++ 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d1c5c44..cb59da3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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 diff --git a/ext/mini_racer_extension/mini_racer_extension.c b/ext/mini_racer_extension/mini_racer_extension.c index afa6e9d..41e5069 100644 --- a/ext/mini_racer_extension/mini_racer_extension.c +++ b/ext/mini_racer_extension/mini_racer_extension.c @@ -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)); diff --git a/ext/mini_racer_extension/serde.c b/ext/mini_racer_extension/serde.c index 7c0bf45..f0370a9 100644 --- a/ext/mini_racer_extension/serde.c +++ b/ext/mini_racer_extension/serde.c @@ -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); } } diff --git a/test/mini_racer_test.rb b/test/mini_racer_test.rb index 99b08d7..35bdd99 100644 --- a/test/mini_racer_test.rb +++ b/test/mini_racer_test.rb @@ -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])')