Add dc_to_s_scratch_t structure to preallocate work buffers for the
base case of divide-and-conquer decimal string conversion. This
eliminates repeated malloc/free calls in the inner loop where digits
are extracted 9 at a time.
Previously, each iteration of the base case loop allocated and freed
a quotient buffer. Now the same two buffers are reused with pointer
swapping, reducing allocation overhead by ~10-18% for large numbers.
Co-authored-by: Claude <noreply@anthropic.com>
When mpz_or or mpz_xor copies an operand when the other is zero,
the copied mpz_t may have an inflated sz field (larger than actual
allocated limbs). Add trim() after mpz_set to normalize the size.
This is a follow-up fix to commit 61aa2234d8 which addressed the
same issue in shift operations.
Co-authored-by: Claude <noreply@anthropic.com>
Add trim() calls after mpz_set/mpz_move in shift operations where
actual bit manipulation is skipped:
- mpz_mul_2exp when e==0 (no shift needed)
- mpz_mul_2exp when bs==0 (limb-only shift)
- mpz_div_2exp when e==0 (no shift needed)
- mpz_div_2exp when bs==0 (limb-only shift)
This prevents inflated sz values from propagating through operations,
complementing the earlier fix to urshift/ulshift when n==0.
Co-authored-by: Claude <noreply@anthropic.com>
When shift amount is 0, urshift() and ulshift() called mpz_set() which
copies data without trimming leading zero limbs. This caused bigint
values to have inflated sz fields, making ucmp() comparisons incorrect.
For example, a 256-bit remainder from division could have sz=18 instead
of sz=8 because the divisor had 18 limbs. This made it compare greater
than values with fewer limbs, even when numerically smaller.
The bug also caused memory leaks when the incorrect comparison led to
taking wrong code paths in division, triggering size overflow exceptions
after memory was allocated.
Co-authored-by: Claude <noreply@anthropic.com>
rational_eq_b was using wrong struct fields (p1->numerator/denominator
which access i.num/i.den) for bigint-backed rationals that use b.num/b.den.
Also added missing MRB_TT_BIGINT case to prevent fallthrough to default
case which caused ping-pong recursion between Rational#== and Integer#==.
Co-authored-by: Claude <noreply@anthropic.com>
Add MRB_TRY/MRB_CATCH to ensure local mpz_t variables are freed when
an exception (e.g., RangeError from shift overflow) occurs during
the all-ones multiplication optimization.
Co-authored-by: Claude <noreply@anthropic.com>
Use stack allocation with zero-initialization and MRB_TRY/MRB_CATCH
to ensure heap-allocated mpz_t data is freed even when an exception
occurs during conversion.
Co-authored-by: Claude <noreply@anthropic.com>
The pack_float, pack_double, unpack_float, and unpack_double functions
accessed float/double bytes via a union with uint8_t array, assuming
bytes[0] is always the LSB. This is only true on little-endian hosts.
Fix by using the same bit-shift approach as the integer pack functions
(pack_quad, unpack_quad, etc). Reinterpret float/double as uint32/uint64
and use shifts to extract/assemble bytes in an endian-independent way.
Fixes: #6698 (s390x test failures)
use the mathematical identity 10^k = 2^k * 5^k to speed up the
divide-and-conquer decimal string conversion. dividing by 5^k
is faster than dividing by 10^k because 5^k has ~30% fewer bits
(log2(5) ≈ 2.32 vs log2(10) ≈ 3.32). the 2^k component is handled
with fast bit shifts.
benchmarks show 3-8% improvement for large numbers:
- 800K bits: 1.00s -> 0.97s
- 1.6M bits: 3.95s -> 3.84s
- 2.4M bits: 8.79s -> 8.46s
Co-authored-by: Claude <noreply@anthropic.com>
Apply Lemire's small table technique: use a 200-byte lookup table to
convert digit pairs (00-99) instead of computing each digit separately.
Reduces operations from 9 to 5 per 9-digit batch in the base case.
Co-authored-by: Claude <noreply@anthropic.com>
Follow the codebase convention of using _recur suffix for recursive
functions (e.g., codedump_recur, dump_recur).
Co-authored-by: Claude <noreply@anthropic.com>
Extract 9 decimal digits at once by dividing by 10^9 instead of 10.
This reduces the number of divisions in the base case by 9x, improving
performance of large bigint to_s conversion by approximately 2x.
Co-authored-by: Claude <noreply@anthropic.com>
For base-10 conversion of numbers with >1000 digits, use a recursive
divide-and-conquer algorithm that splits the number using precomputed
powers of 10. This reduces complexity from O(n^2) to O(n log^2 n).
The algorithm:
1. Precompute 10^1, 10^2, 10^4, 10^8, ... by repeated squaring
2. Find the largest power that splits digits roughly in half
3. Divide by this power to get high and low parts
4. Recursively convert each part, padding low part with zeros
5. Base case: use simple divide-by-10 for <= 1000 digits
Co-authored-by: Claude <noreply@anthropic.com>
The final carry was stored at z->p[y->sz], but when x is larger
than y, this index falls within the already-computed result and
corrupts it. Store at z->p[i] instead, which correctly points to
max(x->sz, y->sz) after all loops complete.
This bug caused incorrect results when adding a small number to
an all-ones number with 1124+ limbs (35968+ bits).
Co-authored-by: Claude <noreply@anthropic.com>
The udiv function had two buggy modifications to Knuth's Algorithm D:
1. A "three-limb pre-adjustment" that only decremented qhat once
2. A "3-limb refinement" loop with incorrect carry handling
These caused incorrect quotients for certain decimal divisions like
10^52 / 10^26. Restored standard Knuth Algorithm D which uses only
2-limb qhat refinement with correction via subtract and add-back.
Co-authored-by: Claude <noreply@anthropic.com>
Numbers with few bits set (popcount <= 8) are multiplied using
shift-add instead of Karatsuba. This is O(k*n) where k is the
popcount, much faster than O(n^1.585) for sparse patterns like
2^100000 + 2^50000 commonly generated by fuzzers.
Co-authored-by: Claude <noreply@anthropic.com>
Add optimized squaring algorithm that exploits symmetry for ~1.5x speedup
over general multiplication. Includes both schoolbook and Karatsuba variants.
- mpz_sqr_basic_limbs: O(n(n+1)/2) multiplications instead of O(n^2)
- mpz_sqr_karatsuba: 3 recursive squarings instead of 3 multiplications
- mpz_sqr: high-level wrapper with fast paths for power-of-2 and all-ones
The optimization triggers when mpz_mul is called with identical pointers
(u == v), which occurs in internal operations like mpz_pow.
Co-authored-by: Claude <noreply@anthropic.com>
Add fast path for multiplying by powers of 2 (2^n). Uses left shift
instead of Karatsuba multiplication: x * 2^n = x << n.
This optimizes "mostly-zero" patterns common in fuzzing tests, where
numbers like 2^2097150 (single bit set) would otherwise trigger slow
Karatsuba multiplication.
Co-authored-by: Claude <noreply@anthropic.com>
Add fast path for multiplying numbers of form 2^n - 1 (all bits set).
Uses algebraic identities:
- (2^n - 1) * (2^m - 1) = 2^(n+m) - 2^n - 2^m + 1
- (2^n - 1) * y = (y << n) - y
These are O(n) operations instead of O(n^1.585) for Karatsuba.
Fuzzing test cases using all-ones patterns now complete in 0.01s
instead of 13+ seconds.
Also raises KARATSUBA_THRESHOLD from 8 to 32 for ~32% speedup
on general large number multiplication.
Co-authored-by: Claude <noreply@anthropic.com>
Reduces recursion overhead for large number multiplication.
Benchmarks show ~32% speedup for million-bit operands.
Co-authored-by: Claude <noreply@anthropic.com>
Add MRB_BIGINT_BIT_LIMIT (1 billion bits / 128MB) to prevent
unreasonably large allocations when left-shifting by huge amounts.
Raises RangeError instead of attempting multi-GB allocations.
Co-authored-by: Claude <noreply@anthropic.com>
mpz_and, mpz_or, and mpz_xor were not calling trim() on their results,
causing inflated size values with trailing zero limbs. This led to
incorrect comparisons in ucmp() and caused udiv() to take wrong code
paths, resulting in memory leaks when exceptions occurred.
Also added defensive overflow checks in mpz_init_heap and udiv.
Co-authored-by: Claude <noreply@anthropic.com>
when converting a shared/static string (IREP_TT_SSTR) to heap-allocated
(IREP_TT_STR), copy the original content to the new buffer.
previously, the original content was lost when allocating new memory,
leaving the first bytes uninitialized. this caused find_pool_str() to
read uninitialized memory via memcmp() when searching for duplicate
strings.
reported by OSS-Fuzz.
Co-authored-by: Claude <noreply@anthropic.com>
When defining a method with a required keyword argument without
parentheses, mruby incorrectly parsed the next line as the default
value:
def foo arg:
123
end
Was parsed as: def foo(arg: 123); end (optional kwarg, empty body)
Should be: def foo(arg:); 123; end (required kwarg, body returns 123)
The fix sets EXPR_ARG lexer state after parsing f_label, making
newlines significant. This prevents the parser from consuming
expressions across line boundaries as default values for keyword
arguments.
Also fixes a pre-existing bug in f_label where tNUMPARAM (type <num>)
was implicitly assigned to $$ (type <id>) without conversion. Now
explicitly uses intern_numparam() to convert numbered parameters to
symbols.
Fixes https://github.com/mruby/mruby/issues/6268
rename mrb_alloca() to mrb_temp_alloc() for clearer naming - the new name
better describes its purpose as GC-managed temporary allocation. keep
mrb_alloca() as a macro alias for backward compatibility.
apply mrb_temp_alloc() to fix potential memory leaks in:
- mruby-strftime: if mrb_str_cat() raises, allocated buffers now cleaned by GC
- mruby-io File.readlink: if mrb_str_new() raises, buffer now cleaned by GC
Co-authored-by: Claude <noreply@anthropic.com>
when a Set contains itself (directly or indirectly), computing its hash
would cause infinite recursion leading to SystemStackError. the exception
during khash rebuild leaked memory.
add recursion detection flag to Set#hash that returns 0 for recursive
references, similar to Ruby's behavior.
Co-authored-by: Claude <noreply@anthropic.com>
prevent resource exhaustion when computing power with extremely large
exponents (e.g., 81.pow(51742871469327219)). the check estimates the
result size and raises RangeError if it would exceed 1 million bits.
Co-authored-by: Claude <noreply@anthropic.com>
Shifting 1 left by MRB_INT_BIT-1 (e.g., 63 on 64-bit) bits into the sign
bit is undefined behavior. Change the overflow check from >= MRB_INT_BIT
to >= MRB_INT_BIT-1 to prevent this.
Co-authored-by: Claude <noreply@anthropic.com>
When year value is close to MRB_INT_MIN, subtracting TM_YEAR_BASE (1900)
causes signed integer overflow. Add underflow check before the subtraction.
Co-authored-by: Claude <noreply@anthropic.com>
The local flock() function for Windows is now dead code since the
HAL refactoring. The Windows implementation is in hal-win-io which
provides mrb_hal_io_flock().
Fixes warning: 'flock' defined but not used [-Wunused-function]
Co-authored-by: Claude <noreply@anthropic.com>
After pattern matching code generation, the sp (stack pointer) must
be restored to match the success path value. The failure path (after
RAISEIF) left sp in a different state, causing incorrect register
allocation in subsequent code like string interpolation.
This caused OP_STRCAT to use the wrong register, leading to
null-dereference when trying to modify a non-string value as a string.
Co-authored-by: Claude <noreply@anthropic.com>
The => pattern matching codegen was doing push() after RAISEIF, even though
RAISEIF never returns. This caused sp to be off by 1 when success and failure
paths joined, resulting in wrong register allocation for subsequent operations.
For string interpolation like "#{ expr => pattern rescue body }", the base
string would be at R2 but STRCAT would incorrectly use R3, causing memory
corruption and crashes.
Test case: %{#{.=>.,. rescue def .()end}} (from oss-fuzz)
Co-authored-by: Claude <noreply@anthropic.com>
rational_new_b() expects both arguments to be bigints, but rational_new_f()
was passing an integer value for the numerator when the exponent was negative.
This caused a segfault in mrb_bint_reduce() which called RBIGINT() on the
integer value.
Test case: 5r**-92 (from oss-fuzz)
Co-authored-by: Claude <noreply@anthropic.com>
mruby-sleep: declare slp_tm before #ifdef _WIN32 block to fix
undeclared variable error in non-Windows branch.
hal-posix-socket: use #if defined(HAVE_SA_LEN) && HAVE_SA_LEN instead
of #ifdef HAVE_SA_LEN, since mruby-socket defines HAVE_SA_LEN to 0 on
non-BSD platforms.
Co-authored-by: Claude <noreply@anthropic.com>
Add #undef lesser after last usage to prevent macro redefinition
warnings when files are amalgamated into a single translation unit.
Co-authored-by: Claude <noreply@anthropic.com>
Remove unused mrb_stat typedef from file.c that conflicted with the
mrb_stat() function in file_test.c when compiled as a single
translation unit.
Fix convert_stat() in hal-posix-io to handle st_atime macro correctly
in both normal and amalgamated builds by extracting time values before
undefining the macros.
Co-authored-by: Claude <noreply@anthropic.com>
When parsing malformed input with many syntax errors (e.g., via eval
with a long garbage string), the parser would continue until the end
of input, causing long execution times.
Add an early termination check in the lexer that returns EOF once
the error count exceeds 10 (same as error_buffer size). This prevents
DoS from inputs like eval("garbage" * 1000).
Co-authored-by: Claude <noreply@anthropic.com>
mpz_div_2exp() was calling mpz_init_heap() on output parameter z
without first freeing z's existing memory. When called from
mpz_barrett_reduce() with pre-allocated temporaries, this caused
memory leaks.
Add mpz_clear(ctx, z) before mpz_init_heap() in both affected code
paths, matching the pattern already used in mpz_mod_2exp().
Fixes ClusterFuzz issue detected with input "8.pow 7*2515881+186,8 ^4>>-509".
Co-authored-by: Claude <noreply@anthropic.com>
Support negative modulus in Integer#pow(exp, mod) with proper Ruby
semantics. Previously, negative modulus caused an infinite loop in
Barrett reduction. Now:
- Use absolute value of modulus for computation
- Apply signed modulo adjustment (result + m for non-zero result
when m is negative)
- Add early return for zero base with positive exponent (0^n = 0)
Co-authored-by: Claude <noreply@anthropic.com>
When right-shifting by more bits than the number contains, the loop
condition `i < x->sz - digs` would underflow (since size_t is unsigned),
causing out-of-bounds memory access.
Fixed by checking if digs >= x->sz upfront and returning zero in that
case, since shifting right by more bits than the number has always
yields zero.
Discovered via ClusterFuzz with input "7<<78<<-772".
Co-authored-by: Claude <noreply@anthropic.com>
In rational_new_f(), the code performed ((mrb_int)1)<<exp without
checking if exp >= MRB_INT_BIT. Shifting by a value >= bit width
is undefined behavior in C.
Also fixed the negative exponent case which incorrectly used
deno >>= exp (right-shift by negative is UB). The correct logic
is deno <<= -exp to multiply denominator by 2^(-exp).
Both cases now check for overflow before shifting and fall back
to bigint operations when necessary.
Discovered via ClusterFuzz with input "92r**11".
Co-authored-by: Claude <noreply@anthropic.com>
Fixed three bugs that caused infinite loops in GCD calculations:
1. mpz_set_int() didn't shrink sz when setting a smaller value.
mpz_realloc() only grows allocations, so setting a 1-limb value
to an mpz_t with sz=3 would leave sz=3, breaking algorithms
that depend on correct sz values.
2. mpz_set_uint64() had the same issue.
3. mpz_gcd() used mpz_init_set() which preserves the sign.
GCD should work with absolute values since gcd(a,b) = gcd(|a|,|b|).
With negative inputs, the sign would oscillate during mod operations,
preventing the Euclidean algorithm from converging.
4. mpz_div_2exp() when e==0 and z==x would corrupt data by calling
mpz_init_heap() which overwrites z->p before copying from x.
These bugs were discovered via ClusterFuzz with complex rational
number calculations.
Co-authored-by: Claude <noreply@anthropic.com>
Fix two functions that could create bigints with sn != 0 but value of 0:
- mpz_mod_limb: single-limb case set r->sn = x->sn even when result was 0
- mpz_mul_2exp: set z->sn = sn unconditionally after zero-producing ops
This inconsistent state caused GCD loop (!zero_p(&b)) to continue with
a zero divisor, eventually causing FPE in mpz_mod_limb with m = 0.
Co-authored-by: Claude <noreply@anthropic.com>
When generating code for pattern matching with potential failures, the
success and failure paths both need to pop the matched value. At
runtime, only one path executes. But during codegen, both pop() calls
affected the compile-time stack pointer (cursp), corrupting register
allocation and causing heap-buffer-overflow when accessing symbol
tables with wrong indices.
Fix by saving/restoring the stack pointer around the branch point, so
each path correctly tracks the stack state independently.
Co-authored-by: Claude <noreply@anthropic.com>
Pattern matching expressions were not pushing a result value in several
code paths when used in value context (e.g., string interpolation).
This caused crashes when the result was expected on the stack.
Fix all code paths in NODE_MATCH_PAT to push the appropriate value:
- 'in' pattern returns true/false
- '=>' pattern returns nil (matches CRuby behavior)
Co-authored-by: Claude <noreply@anthropic.com>