When a hash pattern has 15 or more keys, pack them into an array
before calling __except via OP_SEND with CALL_MAXARGS, since the
OP_SEND instruction can only encode up to 14 direct arguments.
Co-authored-by: Claude <noreply@anthropic.com>
Add Hash#__except that returns a new hash excluding specified keys,
used by the compiler for **rest capture in hash patterns. Takes keys
as direct arguments to avoid array allocation. The compiler passes
matched key symbols directly on the stack via OP_SEND.
Co-authored-by: Claude <noreply@anthropic.com>
Add key existence check using key?() before value access, so that
missing keys correctly fail to match (e.g. {b: 1} no longer matches
{a: nil} pattern). Implement **nil and empty {} exact match via
hash.size == num_keys check. Fix **rest to properly exclude matched
keys using dup + __delete instead of copying the entire hash.
Co-authored-by: Claude <noreply@anthropic.com>
CRuby's Hash#deconstruct_keys simply returns self regardless of
arguments. The mruby-hash-ext version filtered keys, which was
unnecessary since the compiler accesses individual keys via []
after calling deconstruct_keys. The Ruby implementation in
mrblib/hash.rb (returning self) is sufficient and CRuby-compatible.
Co-authored-by: Claude <noreply@anthropic.com>
mpz_abs() internally allocates via mpz_init_heap(), so
pre-allocating abs_x/abs_y with mpz_init_temp() leaked the
original allocations. let mpz_abs() handle allocation directly.
also use divide-first formula (abs_x/gcd)*abs_y to reduce
intermediate product size, and add bint_norm() for the result.
reported by OSS-Fuzz (clusterfuzz-testcase-6501272051318784).
Co-authored-by: Claude <noreply@anthropic.com>
check for overflow using mrb_int_mul_overflow() in the LCM
computation to avoid undefined behavior when the result exceeds
mrb_int range. raises RangeError instead.
reported by OSS-Fuzz (clusterfuzz-testcase-6501272051318784).
Co-authored-by: Claude <noreply@anthropic.com>
move fd, fd2, pid fields before the bitfield flags while keeping
the pointer field last. this preserves the 24-byte struct size
(same as 3.4.0) while restoring fd to offset 0 (same as 3.3.0).
some external gems (e.g. mruby-polarssl) pass struct mrb_io
pointers directly to libraries like mbedtls that expect an int fd
at offset 0. the 3.4.0 reorder moved bitfield flags to offset 0,
causing these gems to read garbage instead of the file descriptor.
fixes#6713
Co-authored-by: Claude <noreply@anthropic.com>
The prek-action already passes --color=always internally,
so passing it again via extra-args causes a CLI error.
Co-authored-by: Claude <noreply@anthropic.com>
The current implementation of `task_init_context` inheriting a receiver from the parent task is unstable and causes critical faults, especially on microcontrollers.
- It leads to a HardFault on devices like Raspberry Pi Pico 2 by accessing a potentially NULL `mrb->c->ci`.
- Even when `mrb->c->ci` is not NULL, this incomplete context copy causes other memory errors (SEGV).
This patch reverts to the safer, previous behavior, that I implemented in picoruby/picoruby, of always initializing a new task's receiver to `top_self`, ensuring predictable and
robust operation.
The issue was likely masked on POSIX systems due to the unpredictable nature of undefined behavior.
Old code:
```c
t = q_ready_;
/* No task ready - check if all tasks are done */
if (!t) {
/* If there are tasks waiting or suspended, idle */
if (q_waiting_ || q_suspended_) {
mrb_hal_task_idle_cpu(mrb);
continue;
```
IRQ possibly happens between `t = q_ready_;` and `if (q_waiting_ || q_suspended_) {` and, for example, a waiting task may move to the ready queue.
As a result, the infinite loop in mrb_task_run unexpectedly breaks in spite of not all the task is dormant.
This patch fixes the issue above by setting the `exitting` condition with a critical section.
the second NULL in the initializer was for the removed gcnext field,
causing "makes integer from pointer" warning on the tt bitfield.
Co-authored-by: Claude <noreply@anthropic.com>
remove per-object gcnext pointer from MRB_OBJECT_HEADER, saving one
word (8 bytes on 64-bit) per object slot. the gray list for tri-color
marking is replaced by a fixed-size stack (MRB_GRAY_STACK_SIZE=1024)
in mrb_gc. when the stack overflows, a linear heap rescan recovers
gray objects.
object slot size: 48 -> 40 bytes (16.7% reduction on 64-bit).
benchmarks show up to 12% RSS reduction on object-heavy workloads
with neutral performance impact.
Co-authored-by: Claude <noreply@anthropic.com>
OUTINT macro checked ayear > INT_MAX, but timegm() later computes
tm_year + TM_YEAR_BASE (1900), which overflows when tm_year is near
INT_MAX. Tighten the upper bound to INT_MAX - TM_YEAR_BASE.
Found by ClusterFuzz.
Co-authored-by: Claude <noreply@anthropic.com>
Same reasoning as the vm.c change - the keyword hash arriving at
C functions via mrb_get_args() is always freshly constructed at the
call site, so duplication is unnecessary.
Co-authored-by: Claude <noreply@anthropic.com>
The keyword argument hash passed to a method is always freshly
constructed at the call site - either by hash_new_from_regs() in
OP_SEND for inline keyword pairs, or by OP_HASH/OP_HASHCAT for
compiler-generated keyword arguments (including the **h splat case
which creates OP_HASH(0)+OP_HASHCAT). Since no caller retains a
reference to this hash, the mrb_hash_dup() was redundant.
Co-authored-by: Claude <noreply@anthropic.com>
Zero-initialize the mirb_editor struct to prevent highlight.enabled
from containing garbage values when stdin is not a tty (e.g. in
bintest). Without this, ANSI color codes could be emitted in
non-interactive mode, breaking output string matching in tests.
Co-authored-by: Claude <noreply@anthropic.com>
Same issue as the pool string fix: the bounds check for
symbol names only validated snl bytes, but the binary
format includes a null terminator. The source pointer
advances by snl+1, so the check must account for it.
Co-authored-by: Claude <noreply@anthropic.com>
The bounds check for IREP_TT_STR pool data only validated
pool_data_len bytes, but the binary format includes a null
terminator after the string content. Both memcpy and the
source pointer advance by pool_data_len+1, so the check
must account for the extra byte.
Co-authored-by: Claude <noreply@anthropic.com>
prek is a faster, Rust-based drop-in replacement for pre-commit.
It reads the same .pre-commit-config.yaml with no changes needed.
Co-authored-by: Claude <noreply@anthropic.com>
The MATCHERR optimization replaced JMPNOT (BS, 4 bytes) with
MATCHERR (B, 2 bytes) and rewound s->pc by 2. When pattern
alternation (e.g. a|B) dispatched a success jump to s->pc before
the optimization, the rewind shifted subsequent instructions and
the jump landed in the middle of the next instruction, causing
out-of-bounds access at runtime.
Replace JMPNOT in-place with MATCHERR+NOP+NOP to keep the same
4-byte size, so s->pc does not change and jump targets stay valid.
Co-authored-by: Claude <noreply@anthropic.com>
Reject MRB_INT_MAX length strings to prevent signed integer overflow
when adding 1 for the null terminator in str_init_normal_capa() and
resize_capa().
Co-authored-by: Claude <noreply@anthropic.com>
CRuby raises SyntaxError for `^a` in pattern matching when `a` is
not a local variable. Previously mruby silently generated an
unconditional fail jump, which also led to bytecode corruption
when combined with alternation patterns.
Co-authored-by: Claude <noreply@anthropic.com>
The JMPNOT-to-JMPIF optimization in NODE_PAT_ALT assumed the fail
chain always ends with OP_JMPNOT (format BS), but NODE_PAT_PIN
generates OP_JMP (format S) when the pinned variable is undefined.
Writing OP_JMPIF at left_fail-2 then corrupts the preceding
instruction's operand, causing out-of-bounds pool access at runtime.
Co-authored-by: Claude <noreply@anthropic.com>
Barrett and Montgomery reduction compute 2^(2k) internally where k
is the modulus bit length. When this exceeds MRB_BIGINT_BIT_LIMIT,
mrb_raise() via longjmp skips cleanup of allocated temporaries.
Add early modulus size check before any heap allocation.
Co-authored-by: Claude <noreply@anthropic.com>
The work buffer size in mpz_montgomery_reduce() was calculated as
x_len + k + 2, which assumed x_len >= k. However, when R^2 mod n
produces a small result, x_len can be much smaller than k.
The Montgomery reduction loop writes k limbs at work[i] for each
iteration i=0..k-1, so the maximum index accessed is work[2k-1].
This requires at least 2k limbs in the work buffer.
Fixed by ensuring work_size is at least 2*k+2 limbs when x_len < k.
Also initialize b->as.heap before mpz_move in bint_set() to ensure
the destination mpz_t has valid initial state.
Co-authored-by: Claude <noreply@anthropic.com>
mpz_mul_sparse allocated temporary mpz_t variables (shifted, temp) that
were leaked when an exception was raised (e.g., RangeError from shift
width too large). bint_mul had the same issue with its output mpz_t z.
Wrap both functions with MRB_ENSURE to guarantee cleanup runs regardless
of exceptions, following the existing pattern used by mpz_mul_all_ones.
Co-authored-by: Claude <noreply@anthropic.com>
Use syntax highlighter for result values instead of single color.
Add support for hash key symbol syntax (e.g., `a:` in `{a: 1}`).
Co-authored-by: Claude <noreply@anthropic.com>
Replace per-symbol mrb_malloc() with a chunk-based string pool that
batches allocations into 4KB chunks. This reduces malloc call count
by ~12x (e.g. 909 vs 10,887 for 10k dynamic symbols) and eliminates
per-allocation malloc metadata overhead (~16 bytes/symbol).
Pool allocations are rounded up to even size to preserve LSB pointer
tagging used for literal detection.
Co-authored-by: Claude <noreply@anthropic.com>
Previously, the identity of the proc object was verified solely based on the identity of irep.
This patch makes the behavior consistent with CRuby.
The reason I noticed this issue was that when adding multiple proc objects with the same irep to a set object, only one was added.
```ruby
p Set.new(Array.new(3) { -> {} }).size
# => 3 (Ruby 4.0)
# => 1 (mruby without this patch)
```
If the block scope is the same, there is only one in CRuby as well.
However, in CRuby, the result of `Proc#to_s` is not affected by the block scope, so it has been changed to be based on the object's address.
The reason no test for `Proc#to_s` was added is that I couldn't determine whether it should be based on `Proc#hash` or the object's address.
```ruby
b = []
t = 3
while t > 0
b << -> {}
t -= 1
end
p Set.new(b).size
# => 1 (Ruby 4.0 and mruby)
p b[0].to_s == b[1].to_s
# => false (Ruby 4.0)
# => true (mruby without this patch)
```
The first keyword argument was dropped because gen_hash() was
called with callargs->keyword_args->cdr instead of
callargs->keyword_args.
Co-authored-by: Claude <noreply@anthropic.com>
Preparation for future grammar simplification that may
require parentheses for method calls with arguments on
the right-hand side of assignments.
Co-authored-by: Claude <noreply@anthropic.com>
These opcodes use BB format instead of BBB, saving 1 byte per call.
In the standard library, this saves ~790 bytes (568 SEND0 + 222 SSEND0).
Co-authored-by: Claude <noreply@anthropic.com>
Change default stack growth from linear (+128) to exponential (1.5x).
This reduces reallocation frequency while maintaining reasonable memory
usage. The minimum growth is still MRB_STACK_GROWTH (128) to ensure
small programs don't over-allocate.
MRB_STACK_EXTEND_DOUBLING (2x growth) remains available for maximum
performance when memory is not a concern.
Co-authored-by: Claude <noreply@anthropic.com>