When sprintf is called with a precision larger than the double's
significand width (e.g. "%.51g"), fixed_width() indexed pow10 tables
out of bounds and produced a negative shift exponent. Cap the
internal digit count to 18 in the %g branch, matching the existing
%e and %f branches; downstream loops already zero-pad to the
caller's precision so visible output is unchanged.
Co-authored-by: Claude <noreply@anthropic.com>
A copied Proc now always carries `MRB_PROC_ORPHAN`, so calling a
`dup`'d block that contains `break` or `return` raises
`LocalJumpError` even while the original yielding method is still on
the stack.
This is stricter than CRuby — which only marks the copy orphan once
the original yielding method returns — but matches mruby's
memory-first design: tracking the original via a back pointer in
RProc would also enlarge the GC mark set. dearblue's option (1) in
the linked issue, accepted for the simpler RProc layout.
Document the divergence in `doc/limitations.md` and add a regression
test in `test/t/proc.rb`.
close#6345
Co-authored-by: Claude <noreply@anthropic.com>
The `dfree` callback registered via `mrb_data_type.dfree` runs from
inside GC sweep, so allocating Ruby objects, calling `mrb_funcall` /
`mrb_yield`, raising exceptions, or otherwise re-entering the VM
can trigger a recursive GC that revisits the same object and causes
double-free (see #6316). Make the rule explicit in the "Wrapping C
Structures" section.
close#6316
Co-authored-by: Claude <noreply@anthropic.com>
`lambda {|a, b=nil|}.curry(3)` used to silently return a curried Proc;
CRuby raises `ArgumentError` because the lambda accepts at most 2 args.
Use `self.parameters` to compute the upper bound (count `:req`/`:opt`
entries, unbounded if `:rest`/`:keyrest` is present) and add the
corresponding range check alongside the existing minimum check.
close#2855
Co-authored-by: Claude <noreply@anthropic.com>
`class String; def split` in mruby-regexp/mrblib/string_regexp.rb
replaced the C-defined String#split rather than overriding it, so
the in-Ruby `return super if pattern.nil?` paths raised
NoMethodError for any `"x".split(...)` call once mruby-regexp was
loaded (the default full-core production binary).
The regression wasn't caught by the test suite because per-gem
tests run under mrb_open_core() with only the gem's dep_list, so
the broken override is never visible from test/t/string.rb
(mruby-test, not mruby-regexp).
Add `alias __split split` at the top of the override class body,
which captures the C-defined method, and change the Ruby override
to delegate via `__split(pattern, limit)` for non-regexp fallback
paths. Add a bintest under mruby-bin-mruby that runs through
bin/mruby (full gem load) to catch this regression class.
Co-authored-by: Claude <noreply@anthropic.com>
The previous commit's prettier pass escaped `*data` to `\*data` in
the SPI#write / SPI#transfer method signatures. The escape is
defensive (prettier avoids any standalone `*` to dodge italic
misparses) but unnecessary in these positions: GFM only treats `*`
as italic when paired with a closing `*`, which never happens in a
method signature. The unescaped form reads more naturally to Ruby
users.
Co-authored-by: Claude <noreply@anthropic.com>
When mrbc compiles multiple input files (e.g. `mrbc -g -o out.mrb
a.rb b.rb`), the bison parser's one-token lookahead can buffer the
final token of one file before partial_hook switches to the next.
By the time bison reduces that token into an AST node,
`mrb_parser_set_filename` has already reset `p->lineno` to 0, so
init_var_header recorded lineno=0 for the previous file's last
statement and codegen propagated the previous instruction's line.
Save the lineno into `prev_file_lineno` immediately before the
reset so init_var_header can restore the correct value when it
detects the lookahead edge case (lineno==0 && filename_index>0).
close#1316
Co-authored-by: Claude <noreply@anthropic.com>
The previous IO.popen capture of mrbc stdout (`-o-`) is vulnerable
to a Windows MinGW race: with parallel rake (-m), stdout pipe
inheritance can allow unrelated `_pp` build-progress messages from
sibling worker threads to leak into the captured pipe content,
corrupting the generated C file (recently seen as `CC build/...`
text inside gem_test.c, causing compile errors at unrelated lines).
Switch to `mrbc -o <tmpfile>` so the output is materialised in a
file before merging into the destination, fully isolating mrbc
from the parent's STDOUT.
Co-authored-by: Claude <noreply@anthropic.com>
MinGW gcc emits -Wmaybe-uninitialized for `digs[0]` in the
`mrb_format_float` function because it cannot prove that
`count_digits()` always returns >= 1. The code is correct
(count_digits's `d == 0` early return makes the lower bound
clear to a human reader), but gcc's flow analysis does not
follow through. Initialize `digs` to silence the false positive
across all MinGW gcc CI configurations.
Co-authored-by: Claude <noreply@anthropic.com>
mruby places `def` written inside `def self.foo` on the receiver's
singleton class (making it a class method of the enclosing class).
CRuby places it as an instance method of the lexical enclosing
class. This is a long-standing divergence that we've chosen to
document rather than change.
close#1536
Co-authored-by: Claude <noreply@anthropic.com>
Add a section explaining that mruby intentionally does not consult
to_int/to_str/to_ary/to_hash for implicit type coercion in built-in
operations, even though identity versions remain defined on the
corresponding built-in types. Note that Float#to_int and Array#to_ary
are not defined, and contrast with explicit conversion methods
(to_i, to_s, to_a) which do work.
ref #2979
Co-authored-by: Claude <noreply@anthropic.com>
Make `Integer#chr("UTF-8")` reject UTF-16 surrogate code points
(U+D800..U+DFFF), and make `String#ord` reject ill-formed UTF-8 byte
sequences (overlong encodings, surrogates encoded as UTF-8, and code
points above U+10FFFF), matching CRuby and RFC 3629.
The `utf8code()` helper now decodes the code point first and then
validates the range per byte length:
len=2: cp >= 0x80 (rejects overlong)
len=3: cp >= 0x800 and not D800..DFFF
len=4: 0x10000 <= cp <= 0x10FFFF
close#2708
Co-authored-by: Claude <noreply@anthropic.com>
Replace the 12 in-tree mrb_funcall_id call sites (all argc=1 or
argc=2) with the typed inline helpers added in the previous commit.
After inlining each call site allocates exactly the slots it needs
on its own frame instead of going through mrb_funcall_id's fixed
16-element argv buffer.
ref #5804
Co-authored-by: Claude <noreply@anthropic.com>
mrb_funcall_id() always reserves a 16-element argv buffer on its
stack frame regardless of the actual argument count. The two new
static inline wrappers allocate exactly one or two argument slots,
saving 100-220 bytes of stack per call after inlining (the savings
depend on mrb_value size under the active boxing configuration).
close#5804
Co-authored-by: Claude <noreply@anthropic.com>
The peephole in gen_addsub() rewrote `q + -n` into OP_SUBI n (and
`q - -n` into OP_ADDI n) by negating n. For numeric receivers this is
equivalent, but for receivers overriding + or - the runtime fallback
dispatches the flipped method, losing the original operator. Restrict
the fold to non-negative immediates; negative falls through to the
normal OP_ADD/OP_SUB path with LOADI of the literal value.
close#2557
ref #2579
Co-authored-by: Claude <noreply@anthropic.com>
Lets a gem author register a block that runs after the user's
`build.gem` block has been processed. Intended for filling in
defaults that depend on user-supplied configuration — for example,
library auto-detection that should only kick in when the user
hasn't explicitly chosen which library to use.
Initialization order is now:
1. block in MRuby::Gem::Specification.new (gem author defaults)
2. block in build.gem (user override)
3. block in post_user_config (gem author finalize, new)
Closes#6212, picked from PR by dearblue with the method renamed
from last_initializer to post_user_config (per matz's review).
Co-authored-by: Claude <noreply@anthropic.com>
Returns an Array of Addrinfo objects for all local IP addresses
(IPv4 and IPv6) on every network interface, matching CRuby's API.
Backed by getifaddrs(3) on POSIX and GetAdaptersAddresses on
Windows (requires iphlpapi.lib, added to the Windows linker libs).
The HAL returns binary sockaddr strings; src/socket.c wraps each
into Addrinfo so the wrapping code stays platform-agnostic.
Resolves the second error reported in #5659 (after the IO.select
fix from the HAL split): "undefined method 'ip_address_list' for
Class". Issue #5659 itself is closed; this lands the missing API.
Ref #5659.
Co-authored-by: Claude <noreply@anthropic.com>
Builds the shared library FROM the existing static archive via
-Wl,--whole-archive, leaving the static-build pipeline (and the
test infrastructure that depends on it) untouched.
Produces in build/host/lib/:
libmruby.so SONAME=libmruby.so.<MAJOR>.<MINOR>
libmruby.so.<MAJOR>.<MINOR> symlink for runtime DT_NEEDED resolution
libmruby.map linker version script: MRUBY_<RELEASE_NO>
libmruby_core.so + symlink (similarly)
Symbol versioning ties to MRUBY_RELEASE_NO (e.g. MRUBY_40000 for
4.0.0). mruby has historically had ABI breaks at TEENY granularity,
so the version tag uses the full release number rather than just
MAJOR.MINOR.
Closes#6239.
Co-authored-by: Claude <noreply@anthropic.com>
Their bodies were nearly identical: same argument parsing,
same proc creation, same target-class plumbing. The only
differences are which method to delegate to in the block-given
case (mrb_obj_instance_eval vs. mrb_mod_module_eval) and
which class to use as the target (singleton vs. self-as-class).
Extract the shared logic into object_eval(self, class_eval).
The two top-level dispatchers become one-line wrappers.
Closes#6579, picked from PR by dearblue.
Co-authored-by: Claude <noreply@anthropic.com>
Adds two API helpers that mirror CRuby's RSTRING_GETMEM idiom:
- ARY_GETMEM(a, ptr, len): inside-mruby helper that takes a
struct RArray* and assigns ptr/len from the embed or heap form.
- RARRAY_GETMEM(a, ptr, len): public wrapper on an mrb_value.
Both expand to a single ARY_EMBED_P check, with a uniqued local so
the array argument is evaluated only once (callers can safely pass
expressions with side effects).
Also type the ARY_NO_EMBED stub of ARY_EMBED_PTR as
((mrb_value*)NULL) instead of integer 0, so it composes cleanly in
pointer expressions like the new ARY_GETMEM.
The build-error issue this originated from (compilation with
MRB_ARY_NO_EMBED) was already fixed differently in master via
#ifndef guards (commit 78658d67e). These additions stand on their
own as new API for downstream gems.
Closes#6712, picked from PR by dearblue.
Co-authored-by: Claude <noreply@anthropic.com>
Two related bugs uncovered by OSS-Fuzz testcase 6692915710853120:
1. add_class allowed unbounded growth of c->classes. Class IDs are
stored in re_inst.a (uint8_t), so any ID >= 256 silently aliases
another class via the cast at emit sites. Worse, c->class_capa
(uint16_t) overflows on doubling past 32768 -> 0, then
mrb_realloc(..., 0) returns NULL, and the next memset(&c->classes[id])
segfaults at NULL+offset. Cap with RE_MAX_CLASSES = 256 (the encoding
limit) and raise via compile_error past that.
2. Once the crash is fixed, the testcase exposes a leak of
c->named_captures: compile_error frees c->code, c->classes, and
c->stripped (commit 3f321f09bc) but missed named_captures. Add it
to the same cleanup block.
Reported by OSS-Fuzz (clusterfuzz testcase 6692915710853120).
Co-authored-by: Claude <noreply@anthropic.com>
In CRuby the inherited method is invoked before Class.new yields to the
block.
An "already initialized error" exception is also removed since the
inherited method is now invoked before initialization and can set
instance variables on a class.
parse_quantifier read digits via min = min * 10 + d with no upper
bound, allowing patterns like /a{1111558833}/ to overflow int and
trigger signed-integer-overflow UB. Even without UB, the value
flows into compile_quantified's emit loop where it would attempt
to emit a billion copies of the atom.
Add RE_MAX_REPEAT = 32768 (the largest value that still fits in
re_inst.offset, the uint16_t jump field) and reject quantifiers
beyond that during parsing via compile_error. Apply the same cap
to the max field.
Reported by OSS-Fuzz (clusterfuzz testcase 6152367367323648).
Co-authored-by: Claude <noreply@anthropic.com>
Winsock APIs (socket, bind, connect, accept, recv, send, ...) report
errors via WSAGetLastError() and do not set errno, so mrb_sys_fail
on Windows was reading a stale or zero errno. Result: every socket
failure raised SystemCallError with errno 0 ("Success") instead of
the appropriate Errno::* class.
Add mrb_hal_socket_set_errno_from_last_error() to the HAL:
- POSIX: no-op (failed calls already set errno)
- Windows: maps WSAGetLastError() to a POSIX errno via wsa_to_errno()
with 32 cases covering the common Winsock error codes; unmapped
codes fall back to EIO. Each case is #ifdef-guarded against older
MSVC CRTs that lack a particular Exxx.
In src/socket.c, route the 22 socket-API failure sites through a new
sock_sys_fail() helper that calls the HAL translator before
mrb_sys_fail. Also fix mrb_hal_socket_set_nonblock() on Windows,
which was returning -1 without setting errno after ioctlsocket
failure.
POSIX behavior unchanged (verified: TCPSocket connect refused ->
Errno::ECONNREFUSED, bind to privileged port -> Errno::EACCES, bad
sockopt -> Errno::EOPNOTSUPP).
Closes#6819, reported by Asmod4n.
Co-authored-by: Claude <noreply@anthropic.com>
Compared to a pure Ruby implementation, this results in faster performance, eliminates recursive calls, and removes the creation of intermediate objects.
The "permutation" implementation in `ary_combination_next()` is slow for C.
However, it does not require a heap other than the index array.
- Modify the `mrb_combination_state` structure to accommodate feature extensions
- Rename `Array#__repeated_combination` to `__combination`
- Consolidate integer checks for arguments into `__combination`
- Since checking for integer types using both `__to_int` and `0 <=>` is redundant, use only `__to_int`
- Since `__combination` now accepts symbols instead of booleans, the call to `to_enum` has also been consolidated
mrb_read_float jumped past the *fp assignment via `goto done` when
the exponent had no digits (e.g., "5e", "5e+"). It returned TRUE
without setting *fp, leaving the caller (mrb_str_to_dbl etc.) to
return whatever was on the stack. MSan flagged this; on most runs
the uninitialized read happens to yield 0.0, so the bug is silently
incorrect rather than crashing.
Refactor the finalization (compute res from d, final_p, sign, etc.)
to run once after the optional-exponent block. The malformed-exponent
case now falls through using the mantissa-only `final_p = trunc - dp`,
producing the same result strtod gives for the same input ("5e" -> 5.0
with endp at 'e'). Float("5e") still raises because mrb_str_len_to_dbl
rejects trailing characters under badcheck.
Reported by OSS-Fuzz (MSan).
Co-authored-by: Claude <noreply@anthropic.com>
On 32-bit platforms whose ABI gives 8-byte members 8-byte alignment
(xtensa, ARM, MIPS, PowerPC, ...), MRB_NAN_BOXING failed to build with
"RVALUE size must be within 5 words" because two structs got padded
past the budget:
- struct RBreak: had an existing MRB_USE_RBREAK_VALUE_UNION workaround
that stores the value as uint32_t[] to avoid forcing 8-byte alignment
on the struct, but the gate only enabled it for MRB_NO_BOXING.
Extend to NAN_BOXING + 32-bit, with a NAN_BOXING-specific get/set
(no separate tt to stash since nan-boxing encodes type in the bits).
- struct RArray: MRB_ARY_NO_EMBED was similarly gated to NO_BOXING;
embedded mrb_value[] forces 8-byte alignment of the inner union and
pads the heap-form layout. Extend the gate to NAN_BOXING + 32-bit.
Both gates now name the structural property (32-bit + mrb_value has an
8-byte aligned member) rather than enumerating boxing modes, so adding
new boxing modes won't silently miss this class of bug again.
i386's System V ABI gives uint64_t only 4-byte alignment, hiding the
problem on x86 -m32; -malign-double simulates the strict-alignment ABI
that exhibits the failure, and is what was used to verify the fix.
Closes#6815, reported by dearblue.
Co-authored-by: Claude <noreply@anthropic.com>
Since these are expressed as "nPk" or "nCk" in mathematics, rename `n` to `k` and `array_size` to `n`.
Additionally, rename the parameters `#__repeated_combination` and `#__combination_init` from `n` to `k`.
However, the parameter `n` in `#repeated_permutation` and `#repeated_combination` remains unchanged to align with CRuby.
compile_error is the chokepoint for all regex-compile errors;
mrb_raisef longjmps out of re_compile, abandoning the stack-local
re_compiler struct. Three connected bugs:
1. Memory leak: c->code and c->classes (grown by emit/add_class
via mrb_realloc) were never freed before raising, leaking on
any compile error like /[/. c->stripped was already cleaned up
here for the same reason; the other two buffers were missed.
2. Use-after-free: c->src aliases c->stripped when RE_FLAG_EXTENDED
is set, but the original code freed c->stripped before passing
c->src to mrb_raisef's "%s" formatter. Format the message into
an mrb_value first (mruby's GC-managed string survives the
longjmp), then free, then raise.
3. Heap-buffer-overflow: strip_extended returns a non-NUL-terminated
buffer of size len. Even with format-before-free, "%s" called
strlen and read past the buffer end. Use mruby's %l directive
which takes an explicit (char*, size_t) and avoids strlen.
Reported by OSS-Fuzz (clusterfuzz testcase 5394267353972736).
Co-authored-by: Claude <noreply@anthropic.com>
Two UBSan issues exposed by sprintf("%f", 1e-7) and similar:
1. uscale() shifted hi by c.s without bounding c.s, hitting UB
when c.s >= 64. The mask line had `c.s & 63`, but the actual
`hi >> c.s` line did not, so the partial guard was incomplete.
On x86 the hardware silently masks the shift, producing wrong
output ("1844674407370.955078" for 1e-7) instead of crashing.
When c.s >= 64 the value rounds to 0 with sticky=1, so we can
bail early.
2. count_digits(0) called bits_len64(0) -> clz64(0), which is UB.
The only other bits_len64 caller already guards d == 0; align
count_digits with that pattern. Returning 1 (since "0" is one
digit) preserves output formatting.
Reported by OSS-Fuzz (clusterfuzz testcase 5210395240628224).
Co-authored-by: Claude <noreply@anthropic.com>