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>
`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>
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>
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>
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>
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>
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>
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
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>
The first-byte bitmap (bm[16]) is intentionally ASCII-only
(include/re_internal.h:75 documents it as 128 bits / ASCII), and
the matcher at re_exec.c:39 short-circuits for bytes >= 128. But
first_set_walk's RE_CHAR case wrote bm[a >> 3] without checking
a, overflowing the 16-byte stack buffer for any pattern
containing a byte >= 128.
When a >= 128, return FALSE so compute_first_set marks the filter
unusable, matching the bail-out pattern already used for RE_NCLASS
and RE_ANY. The pattern still compiles and matches; only the
first-byte optimization is skipped.
Reported by OSS-Fuzz (clusterfuzz testcase 4909069193510912).
Co-authored-by: Claude <noreply@anthropic.com>
Since `Kernel#puts` is undefined in unit tests, using `Kernel#print` is required.
Previously, for example, running `rake test` with the following build configuration caused the tests to fail.
```ruby
MRuby::Build.new do
toolchain
enable_debug
enable_test
enable_bintest
gem core: "mruby-bin-mrb"
end
```
io_puts_ary recursed unconditionally on nested arrays. For cyclic
arrays (a = []; a << a; puts a) or pathologically deep arrays,
this caused a C stack overflow.
Add a depth cap (IO_PUTS_MAX_DEPTH = 16); on overflow, write
"[...]\n" and return, matching CRuby's behavior on cycles. The
pattern mirrors mruby-set's MAX_NESTED_DEPTH for the same problem
shape (pure C recursion not dispatched as a Ruby method).
Reported by OSS-Fuzz (clusterfuzz testcase 6233530857488384).
Co-authored-by: Claude <noreply@anthropic.com>
The mruby C style places `else` on its own line. Reformat the
remaining `} else if (...)` occurrence in the C action block of
NODE_SYMBOLS dump.
Co-authored-by: Claude <noreply@anthropic.com>
`build.bins` is for compiled binaries with bare names; entries are
fed through `exefile()` in `tasks/bin.rake`, which appends
`exts.executable` (e.g. `.exe`) when no extension is present.
When `ENV['OS']` is not `Windows_NT` but the toolchain has
`exts.executable=".exe"` (e.g. cross builds, or environments where
`OS` is unset under visualcpp), `mruby-bin-config` ended up with a
file task at `.../mruby-config` while rake asked for
`.../mruby-config.exe`, aborting with "Don't know how to build task".
Skip the `bins` path and call `build.define_installer` directly with
`mruby_config_path`, mirroring the existing `iscross` branch. The
file task path now always matches the script content's extension
(`.bat` on Windows, no extension elsewhere).
close#6807, reported by UENO, M. (@eunos-1128).
Co-authored-by: Claude <noreply@anthropic.com>
This reverts commit a69d12aa54.
The diagnostic served its purpose: it isolated the failure to
Winsock's getaddrinfo for the "localhost" hostname. The permanent
fix (skip guard) is now in place, so remove the temporary probe.
Co-authored-by: Claude <noreply@anthropic.com>
Addrinfo.getaddrinfo("localhost", ...) and Addrinfo.foreach("localhost",
...) crash on GitHub Actions Windows runners (Windows Server 2022 and
2025, both mingw-gcc and MSVC). Diagnostic showed 127.0.0.1 as a
numeric literal resolves fine via Winsock getaddrinfo, but "localhost"
as a hostname returns WSAHOST_NOT_FOUND under every address family
(AF_INET, AF_INET6, AF_UNSPEC, nil). PowerShell Resolve-DnsName on
the same runner succeeds, so the failure is Winsock-specific rather
than OS-level. Earlier workarounds (Dnscache service start, hosts
file append) had no effect.
Skip both tests on Windows with the existing SocketTest.win? guard,
matching the pattern already used for Addrinfo.unix and
Addrinfo#afamily. Linux and macOS coverage is unaffected.
Co-authored-by: Claude <noreply@anthropic.com>
Addrinfo.getaddrinfo("localhost", 53, AF_INET, SOCK_STREAM) crashes
on GitHub Actions Windows runners with WSAHOST_NOT_FOUND, while the
same call works on Linux and macOS. Earlier hypotheses (winsock link
missing; hosts file not populated) have been ruled out on CI.
Probe five variants in one run to isolate the failing condition:
numeric literal 127.0.0.1, localhost with AF_UNSPEC, localhost with
nil family, localhost with AF_INET6, and the original AF_INET path.
Results go to stdout via puts so they are visible in CI logs. This
commit is temporary and will be reverted once the real fix lands.
Co-authored-by: Claude <noreply@anthropic.com>
Commit be6413f0d86a ("mruby-task: migrate HAL to ports/ directories")
moved the Windows HAL source into mruby-task/ports/win/ but dropped
the linker.libraries declaration that previously lived in
hal-win-task/mrbgem.rake. Both mingw and MSVC builds now fail to link
task_hal.o/obj with undefined references to timeBeginPeriod,
timeEndPeriod, timeSetEvent, and timeKillEvent. Re-declare winmm
under a for_windows? guard.
Co-authored-by: Claude <noreply@anthropic.com>
Commit 9965f11cfe1c ("mruby-io: migrate HAL to ports/ directories")
moved the Windows HAL source into mruby-io/ports/win/ but dropped the
linker.libraries declaration that previously lived in
hal-win-io/mrbgem.rake. Both mingw and MSVC builds now fail to link
with undefined references to closesocket, WSAStartup, WSACleanup,
select, and WSAGetLastError referenced from io.c and io_hal.c.
Re-declare ws2_32 under a for_windows? guard.
Co-authored-by: Claude <noreply@anthropic.com>
Commit d9e107c801 ("mruby-socket: migrate HAL to ports/ directories")
moved the Windows HAL source into mruby-socket/ports/win/ but dropped
the linker.libraries declarations that previously lived in
hal-win-socket/mrbgem.rake. Both mingw and MSVC builds now fail to
link with undefined references to WSAStartup, socket, send, and other
Winsock APIs. Re-declare wsock32 and ws2_32 under a for_windows? guard.
Co-authored-by: Claude <noreply@anthropic.com>
Cover zero operands, power-of-2 fast path, negative operands,
balanced multi-limb pairs with a shared Fibonacci factor, highly
unbalanced pairs (to exercise the Euclidean fallback), and
Fibonacci neighbors (always coprime). Declare a test dependency
on mruby-numeric-ext since Integer#gcd is defined there.
Co-authored-by: Claude <noreply@anthropic.com>
Replace the classical Euclidean main loop (mpz_mod per iteration)
with Stein's binary GCD: subtract + factor out trailing 2s on
odd-maintained operands. Keep an mpz_mod fallback for heavily
unbalanced pairs (the smaller operand has at least two fewer limbs)
where one long division replaces many Stein subtracts.
The old loop allocated a temporary mpz_t every iteration to hold the
mod result; the Stein loop is allocation-free thanks to the in-place
paths in mpz_sub and mpz_div_2exp. This matches the "memory first"
priority and also happens to be faster on typical inputs because
mpz_mod's setup cost dominates when the quotient is small (the
classical Fibonacci-neighbor worst case).
Also add a small mpz_swap helper used by the new loop.
Benchmark (bin/mruby benchmark/bm_bigint_gcd.rb, median of 3):
case before after ratio
single-limb 42 ms 50 ms 1.19 (fast-path
unchanged;
noise)
fib(200) vs fib(201) 82 ms 23 ms 0.28
balanced ~700-bit shared 46 ms 32 ms 0.70
unbalanced big vs small 35 ms 28 ms 0.80
power-of-2 path 38 ms 42 ms 1.11 (fast-path
unchanged;
noise)
balanced ~2800-bit shared 19 ms 15 ms 0.79
Co-authored-by: Claude <noreply@anthropic.com>
The preceding comment claimed "Binary GCD (Stein's algorithm)",
but the multi-limb main loop is classical Euclidean using mpz_mod.
Only the prelude (factoring out common 2s, single-limb and
power-of-2 fast paths) is Stein-flavored. Describe what the code
actually does so readers are not misled.
Co-authored-by: Claude <noreply@anthropic.com>
Per gemini-code-assist review on #6791: replace member-by-member
assignment with C89/C++98-style positional aggregate initialization,
keeping the macro a single declaration and matching the existing
pool_storage initializer style in the same macro.
Co-authored-by: Claude <noreply@anthropic.com>
The mrb_match_data struct stores `source` and `regexp` as plain
mrb_value members of a C-allocated struct, which the GC does not
scan. Under MRB_GC_STRESS the source string could be collected
while the MatchData was still alive, causing md[0] to read freed
memory (observed as "\xff\xff\xff").
Also stash source and regexp as instance variables on the MatchData
object so they remain reachable via the object's iv_tbl during GC.
The C struct members continue to provide fast direct access, and
no other call sites need to change.
Co-authored-by: Claude <noreply@anthropic.com>
Add hw-adc gem with analog-to-digital conversion support:
- Common Ruby API with read, read_raw, and read_voltage
- ports/esp32/ using ESP-IDF ADC oneshot driver
- ports/rp2040/ using Pico SDK ADC hardware
Based on picoruby-adc by HASUMI Hitoshi.
Co-authored-by: Claude <noreply@anthropic.com>
Add hw-pwm gem with pulse width modulation support:
- Common Ruby API with frequency, duty, period_us, pulse_width_us
- ports/esp32/ using LEDC peripheral
- ports/rp2040/ using Pico SDK PWM hardware
Based on picoruby-pwm by HASUMI Hitoshi.
Co-authored-by: Claude <noreply@anthropic.com>
Add hw-spi gem with SPI communication support:
- Common Ruby API with write, read, and full-duplex transfer
- ports/esp32/ using ESP-IDF SPI master driver
- ports/rp2040/ using Pico SDK
Based on picoruby-spi by HASUMI Hitoshi.
Co-authored-by: Claude <noreply@anthropic.com>
Move hal-posix-task and hal-win-task into
mruby-task/ports/posix/ and mruby-task/ports/win/.
Remove HAL auto-detection logic from mrbgem.rake. Update
cosmopolitan.rb to use conf.ports :posix instead of explicit
hal-* gem references.
Co-authored-by: Claude <noreply@anthropic.com>
Move hal-posix-socket and hal-win-socket into
mruby-socket/ports/posix/ and mruby-socket/ports/win/.
Remove HAL auto-detection logic from mrbgem.rake.
Co-authored-by: Claude <noreply@anthropic.com>
Move hal-posix-dir and hal-win-dir into mruby-dir/ports/posix/
and mruby-dir/ports/win/. Remove HAL auto-detection logic from
mrbgem.rake.
Co-authored-by: Claude <noreply@anthropic.com>
Move hal-posix-io and hal-win-io into mruby-io/ports/posix/ and
mruby-io/ports/win/. Platform sources are now compiled
automatically based on conf.ports setting. Remove HAL
auto-detection logic from mrbgem.rake.
Co-authored-by: Claude <noreply@anthropic.com>
Move hw-esp32-uart and hw-rp2040-uart into hw-uart/ports/esp32/
and hw-uart/ports/rp2040/ using the new ports build system.
Co-authored-by: Claude <noreply@anthropic.com>