Add ability to generate combined mruby.h and mruby.c files for
single-file embedding, similar to SQLite's amalgamation.
Usage: rake amalgam
Output: build/<target>/amalgam/mruby.{h,c}
Features:
- Headers concatenated in dependency order with guards stripped
- Sources concatenated with proper ordering (core, gems, mrblib)
- X-macro headers (ops.h) inlined at each include point
- Local includes automatically inlined
- Handles both src/ and core/ gem directory conventions
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>
Use self.begin/self.end instead of first/last to compute hash for
ranges. The first/last methods raise RangeError for endless/beginless
ranges, but the internal begin/end accessors return nil safely.
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>
The direct literal generation optimization for parallel assignment was
using the RHS count as the loop bound but only filling registers for
LHS variables. When RHS has more elements than LHS (e.g., `a,=1,2`),
this caused uninitialized register indices to be used, generating
garbage opcodes that crashed the VM.
Fix by counting LHS variables and only applying the optimization when
LHS and RHS counts match exactly.
Co-authored-by: Claude <noreply@anthropic.com>
Add precedence declarations to resolve the ambiguity between:
- One-line pattern match: `arg in pattern`
- Case/in clause: `case expr; in pattern; end`
When seeing `arg in`, the parser should shift to parse `arg in pattern`
as a complete expression (matching CRuby behavior), not reduce `arg`
to start a case clause.
Changes:
- Add keyword_in to %nonassoc precedence declarations
- Add %prec tLOWEST to the plain `arg` reduction rule
This eliminates all bison shift-reduce conflicts (was 2, now 0).
Co-authored-by: Claude <noreply@anthropic.com>
Remove non-standard `symbol tASSOC p_as` rule from hash pattern
elements. This rule conflicted with the as-pattern rule and caused
`:foo => x` to be incorrectly parsed as a hash pattern instead of
an as-pattern.
CRuby only supports label syntax (foo:) for hash pattern keys,
not hashrocket syntax (:foo =>). This change aligns mruby with
CRuby behavior and reduces bison shift-reduce conflicts from 2 to 1.
Before: `case :foo; in :foo => x; end` raised NoMethodError
After: `case :foo; in :foo => x; end` binds x to :foo
Co-authored-by: Claude <noreply@anthropic.com>
The p_value grammar rule passed raw tSTRING token (a (len . str) cons
cell) directly to new_pat_value() without wrapping it as a proper AST
node. When codegen processed this malformed node, it read the length
field as the node type, causing misinterpretation and crash.
Wrap tSTRING with new_str(p, list1($1)) to create a proper NODE_STR,
consistent with how the primary:string rule handles strings.
Found by ClusterFuzz (oss-fuzz/mruby_fuzzer).
Co-authored-by: Claude <noreply@anthropic.com>
When rand is called with a range exceeding UINT32_MAX (e.g.,
rand(2..4294967297)), the span value could overflow when cast
to uint32_t, causing division by zero in the modulo operation.
Add 64-bit path for MRB_INT64 builds that combines two 32-bit
randoms when the range exceeds 32 bits.
Found by ClusterFuzz (oss-fuzz/mruby_fuzzer).
Co-authored-by: Claude <noreply@anthropic.com>
Replace for..in loops with while loops to avoid closure overhead.
Cache constants in local variables to avoid repeated lookups.
59% faster (7.4s -> 3.0s).
Co-authored-by: Claude <noreply@anthropic.com>
Optimized to avoid Math.sqrt by squaring the threshold
(sqrt(x) < 1000 => x < 1000000) and caching zr*zr/zi*zi
to avoid redundant computation. 29% faster than naive version.
Co-authored-by: Claude <noreply@anthropic.com>
Add syntax highlighting to mirb's multi-line editor with support for:
- keywords (def, if, class, end, etc.) in magenta
- strings ("...", '...', %q{...}) in green
- comments (#...) in gray
- numbers (42, 3.14, 0xff) in cyan
- symbols (:foo) in yellow
- constants (Array, Foo) in bold yellow
- instance variables (@var) in blue
- global variables ($var) in bold blue
Features:
- auto-detects light/dark theme via COLORFGBG env var
- MIRB_THEME=light/dark for explicit override
- method calls like obj.class correctly not highlighted as keywords
- enabled automatically when terminal supports color
Co-authored-by: Claude <noreply@anthropic.com>
Estimate initial buffer size based on format string to reduce
reallocations. The new formula uses format string length plus
120 bytes base, plus 24 bytes per format specifier, capped at 4096.
This reduces reallocations by ~60% in typical use cases and
improves performance by 2-21% depending on output size.
Co-authored-by: Claude <noreply@anthropic.com>
Ran `pre-commit run --all-files --hook-stage manual` and this ran prettier.
We had a Markdown table reformated and an backslash escape added.
A link was also fixed.
Tested both the standard and manual hooks pass
Use _WIN32 instead of _MSC_VER to provide strndup implementation
for all Windows compilers including MinGW/MSYS.
Co-authored-by: Claude <noreply@anthropic.com>
- Define strdup as _strdup on MSVC to avoid deprecation warning
- Add strndup implementation for Windows (not available in MSVC)
Co-authored-by: Claude <noreply@anthropic.com>
- rename NEWS to NEWS.md with markdown format
- document pattern matching (case/in) feature
- document new gems (mruby-task, mruby-benchmark, mruby-strftime)
- document mirb improvements
- document HAL platform abstraction
- document C API changes
- list fixed GitHub issues
- list 101 merged pull requests from community contributors
- list security fixes
Co-authored-by: Claude <noreply@anthropic.com>
Array#find is an optimized version of Enumerable#find for arrays,
using direct index access instead of each iterator.
Array#rfind finds from the end of the array, returning the first
match when scanning backwards.
Both methods support the ifnone parameter for default values.
Co-authored-by: Claude <noreply@anthropic.com>
Add notes section explaining the optimization behavior:
- Which functions are used for direct access
- When fallback to method dispatch occurs
- Why subclasses can override []/[]=
Co-authored-by: Claude <noreply@anthropic.com>
Replace mrb_obj_class() with direct mrb_obj_ptr(va)->c access:
- Skips unnecessary mrb_immediate_p() check (these types are never immediate)
- Skips mrb_class_real() traversal for singleton classes
- Objects with singleton methods now fall back to method dispatch
(correct behavior since they might have overridden []/[]=)
Co-authored-by: Claude <noreply@anthropic.com>
Add inline optimizations for Array#[]= and Hash#[]= in OP_SETIDX,
matching the pattern established for OP_GETIDX:
- Array class: use mrb_ary_set() directly (integer index only)
- Hash class: use mrb_hash_set() directly
- Subclasses: fall back to method dispatch (can override []=)
- String: unchanged (complex 2-3 argument signature)
Co-authored-by: Claude <noreply@anthropic.com>
Apply the same pattern as the Array/Hash fix: the OP_GETIDX optimization
now only applies to instances of the String class itself. Subclasses
fall back to method dispatch, allowing them to override the [] method.
Co-authored-by: Claude <noreply@anthropic.com>
Apply the same pattern as the Hash fix: the OP_GETIDX optimization
now only applies to instances of the Array class itself. Subclasses
fall back to method dispatch, allowing them to override the [] method.
Co-authored-by: Claude <noreply@anthropic.com>
The OP_GETIDX optimization now only applies to instances of the Hash
class itself. Subclasses fall back to method dispatch, allowing them
to override the [] method. This fixes compatibility with libraries
like mruby-hashie that rely on aliasing/overriding [] in subclasses.
Trade-off: Hash#[] cannot be overridden on the Hash class itself
(only on subclasses). This is a reasonable semantic for mruby since
subclassing is the proper pattern for customization.
Co-authored-by: Claude <noreply@anthropic.com>
The previous optimization for converting JMPNOT+JMP to JMPIF in
alternative patterns had two bugs:
1. It triggered incorrectly for nested alternatives like `1 | 2 | 3`
(parsed as `((1|2)|3)`), causing memory corruption.
2. The chain end detection was wrong - it checked `prev_offset == 0`
but the chain actually ends when `(pos+2) + offset == 0`.
Fix by:
- Only applying optimization when left pattern is not NODE_PAT_ALT
- Correctly detecting chain end by checking if next_addr == 0
- Properly unlinking the last JMPNOT from the fail chain
Co-authored-by: Claude <noreply@anthropic.com>
When the match target is a known array literal, apply these optimizations:
1. Skip #deconstruct call - array literals are already arrays
2. Skip runtime #size check - verify size at compile time
3. Use GETIDX opcode instead of SEND :[] for element access
For the general (non-array-literal) case, improve efficiency by:
- Using EQ opcode instead of SEND :== for size comparison
- Using GE opcode instead of SEND :>= for minimum length check
This reduces bytecode size by ~27% for patterns like:
[1,2] in Array|[Integer,Integer]
Co-authored-by: Claude <noreply@anthropic.com>
When matching array/hash element patterns like `[Integer]` against
values, the element register was being overwritten by codegen before
the comparison. This caused `[1] in [Integer]|[String]` to incorrectly
return false because the bytecode was effectively doing `1.===(Integer)`
instead of `Integer.===(1)`.
Fix by preserving the element value with push() before calling
codegen_pattern, so the element stays at cursp()-1 while the pattern
value is generated at cursp().
Co-authored-by: Claude <noreply@anthropic.com>
In alternative patterns (e.g., `Integer|String`), when the left pattern
has a single JMPNOT immediately before the JMP to success, convert the
JMPNOT to JMPIF and eliminate the JMP instruction.
This saves 3 bytes per optimized alternative pattern.
Co-authored-by: Claude <noreply@anthropic.com>
When pattern matching an array literal against an array pattern with
matching sizes (e.g., `[1,2] => a,b`), skip the runtime calls to
#deconstruct and #size. Instead, directly extract elements using the
VM's GETIDX opcode.
This reduces bytecode from 72 to 28 bytes (61% reduction) and
eliminates 4 method calls per pattern match.
Co-authored-by: Claude <noreply@anthropic.com>
For patterns with a single failure check (like `1 => String`), invert
JMPNOT to JMPIF and eliminate the following JMP instruction.
Before: JMPNOT fail; JMP end; fail: error; end: (8 bytes for jumps)
After: JMPIF end; error; end: (4 bytes for jump)
The optimization only applies when:
1. There's exactly one JMPNOT in the failure chain
2. The JMPNOT is immediately before the JMP (no code between)
Co-authored-by: Claude <noreply@anthropic.com>
For `1 => a`, generate the same bytecode as `a = 1` by leveraging
gen_move's peephole optimization. The peephole optimizer rewrites
LOADI+MOVE into a single LOADI to the target register.
Before: LOADI_1 R2; MOVE R1 R2 (6 bytes)
After: LOADI_1 R1 (3 bytes)
Co-authored-by: Claude <noreply@anthropic.com>