Commit Graph

18878 Commits

Author SHA1 Message Date
Yukihiro "Matz" Matsumoto 20d8e202a2 Merge pull request #6866 from 0x1eef/task_oneloop
task: return `nil` when given a nested call to `Task.run`
2026-05-28 11:28:07 +09:00
0x1eef 4bdde3a0a8 task: add test 2026-05-27 23:27:51 -03:00
0x1eef ee82a7fcc6 fix: wrap mrb_task_run in MRB_TRY/MRB_CATCH 2026-05-27 23:19:43 -03:00
0x1eef 819156e678 task: return nil when given a nested call to Task.run
When you try to start an event loop inside an event loop,
the mruby process will SIGSEGV:

```ruby
Task.new { Task.run }
Task.run
```

This change turns the second call to `Task.run` into a noop
that returns nil instead.

Fix #6865
2026-05-27 23:10:09 -03:00
Yukihiro "Matz" Matsumoto d1289ac8f9 vm.c: defer task switches during gc.iterating
RETURN_IF_TASK_STOPPED used to bail out of mrb_vm_exec as soon as
task.switching was set, even when the exec was called re-entrantly
from inside a heap-walk callback (e.g. ObjectSpace.each_object via
mrb_yield). Bailing in that situation only unwinds the inner exec,
leaving the outer C iteration to keep calling the callback. The
call-info stack drifts on each subsequent mrb_yield, and the program
either trips the cibase assertion in mrb_vm_run or crashes in
__longjmp.

Hold off the switch while mrb->gc.iterating is set so the heap walk
finishes intact; the switch then fires at the next OP boundary once
the walk releases the flag. MRB_TASK_STOPPED is intentionally not
deferred, since exiting promptly is still correct when the task
itself is going away.

Fixes #6862.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-28 07:27:37 +09:00
Yukihiro "Matz" Matsumoto f5a5bfcbf6 limitations.md: replace binding note with general mrbgem pattern
The Kernel#binding entry was treating "feature provided by mrbgem"
as a per-method limitation, which does not scale. mruby implements
much of Ruby's standard surface area through mrbgems, so listing
each one would grow without bound.

Replace it with a single top-level note explaining the general
pattern: which features are available depends on the linked gems,
and a NoMethodError on a familiar Ruby method usually points to a
missing gem rather than a true mruby gap.

Refs #6861.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-27 08:49:07 +09:00
Yukihiro "Matz" Matsumoto 012691279d mruby-string-ext: skip scrub UTF-8 assertions on non-UTF-8 builds
When MRB_UTF8_STRING is undefined, String#scrub is a no-op that
returns the receiver as-is (the #else branch added in ccb62ceb57).
The unit tests assumed UTF-8 semantics unconditionally, so a build
of just mruby-string-ext without UTF-8 strings failed all five scrub
tests.

Guard the UTF-8-dependent assertions with `skip unless "あ".length
== 1`, and add a paired test that asserts the no-op behaviour on the
non-UTF-8 build (skipped on UTF-8 builds).

Verified against the build config from the report:
  - UTF-8 (host-debug): 5 tests pass, 1 skip
  - non-UTF-8 (noutf8):  5 skip, 1 test pass

Closes #6860.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-26 23:10:45 +09:00
Yukihiro "Matz" Matsumoto ccb62ceb57 mruby-string-ext: add String#scrub
Replaces each maximal run of invalid UTF-8 bytes with a replacement
string (U+FFFD by default), returning a valid UTF-8 copy. Mirrors
CRuby's String#scrub (Feature #6752) -- the recovery counterpart to
the existing String#valid_encoding? detection API.

Validation matches utf8code() in src/string.c after the RFC 3629 /
Unicode D93b conformance fixup (#2708): overlong encodings, UTF-16
surrogates, and codepoints above U+10FFFF are all treated as invalid.
This is stricter than the existing mrb_utf8len()-based check used by
valid_encoding?, so a string can report valid_encoding? = true and
still get scrubbed; aligning valid_encoding? is a follow-up.

The block form lives in mrblib on top of two C primitives -- __scrub
and __scrub_chunks -- to avoid VM re-entry from C per CLAUDE.md.
Non-String block return values are coerced via to_s (CRuby raises
TypeError instead; the choice is locked in by test).

Closes #6859.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-25 23:36:42 +09:00
Yukihiro "Matz" Matsumoto 4f398f6126 mruby-task: prefix queue helpers with mrb_task_
`q_insert_task` and `q_delete_task` were exporting bare `q_*` names
from libmruby.a -- single-letter prefixes don't belong to the gem's
namespace and risk colliding with anything else linked in.

Rename to `mrb_task_q_insert` / `mrb_task_q_delete`, matching the
`mrb_task_*` convention already used for the rest of the gem's
externally visible symbols. Callers in task.c and task_queue.c are
updated to the new names.

Closes #6858.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-25 06:48:13 +09:00
Yukihiro "Matz" Matsumoto 19c857a773 mruby-regexp: prefix exposed engine entry points with mrb_re_
The Pike VM and pattern compiler were exporting bare names like
`re_compile`, `re_exec`, `re_free`, `re_is_word_char`, `re_utf8_charlen`,
`re_utf8_decode`. `re_exec` in particular collides with the obsolete
BSD libc function of the same name (still present on FreeBSD/NetBSD
base), so embedding mruby alongside platform regex could surface a
link-time symbol clash.

Rename all six entry points to `mrb_re_*` to keep the gem's external
symbols inside mruby's namespace. Source file names and the public
header path are unchanged.

Refs #6858.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-25 06:09:11 +09:00
Yukihiro "Matz" Matsumoto f6564c7db1 class.c: make define_method_m static
The helper is used only inside class.c (mrb_mod_define_method_m,
mod_define_method, define_singleton_method) -- no need to expose it
through libmruby.a. Drops one of the non-`mrb_*` linkage symbols
flagged in #6858.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-25 06:06:05 +09:00
Yukihiro "Matz" Matsumoto 36dd9eab88 vformat.rb: fix format/arg type mismatch in %!d test case
The test pushed mrb_int via vf.i but the format read int via %!d. On
x86_64/aarch64 the va_arg slots overlap so reading the low 32 bits of
the pushed int64 returned the right value, but on strict-alignment
ABIs (MIPS o32) va_arg(ap, int) reads the alignment padding and the
format prints 0 instead of the value.

Make the format match the helper: vf.i pairs with %!i (reads mrb_int),
matching the surrounding lines 44-50 convention and the "inspect
mrb_int" label.

Reported by vobloeb in #6857.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-24 10:31:14 +09:00
Yukihiro "Matz" Matsumoto b1adab4e87 Merge pull request #6856 from vobloeb/patch-1
test/bintest.rb: tokenize ENV['EMULATOR'] via Shellwords.split
2026-05-23 12:16:39 +09:00
Yukihiro "Matz" Matsumoto d21eceb286 mruby-regexp: disable first-byte skip when pattern can match empty
first_set_walk returned TRUE when it reached RE_MATCH via epsilon
transitions, but that's exactly the case where the optimization is
wrong: an empty-matchable pattern can start matching at any position,
including bytes that aren't in the computed first-byte set. The
skip-ahead loop in pike_vm then advanced past valid empty-match
positions, producing a match at the wrong offset (e.g. /a?/.match("b")
reported the empty match at index 1 instead of 0).

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-23 09:58:31 +09:00
Yukihiro "Matz" Matsumoto b9b8186f00 mruby-regexp: don't bump jump offsets that already point at insertion site
insert_inst was incrementing every offset >= pos, but an offset equal to
pos already points to the new instruction's slot -- bumping it shifts
the target onto whatever code got displaced (typically the body of the
quantified atom). For patterns like /a?b?/ the SPLIT for `a?` then
landed on `CHAR 'b'` instead of the new SPLIT for `b?`, so the "skip a"
thread tried to consume 'b' and died, and both atoms failed to match
zero characters at once.

Fixes #6853.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-23 09:58:30 +09:00
Yukihiro "Matz" Matsumoto cddaec7264 mruby-compiler: bail out of array-literal pattern match opt on splat
The optimization that skips `deconstruct` and the size check when the
case/in value is an array literal trusted node count, ignoring splat.
An element like `*a` expands at runtime, so [*a] was treated as length
1 and matched only patterns of that length.

Fixes #6854.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-23 09:58:30 +09:00
Yukihiro "Matz" Matsumoto 3aea2f7e67 Merge pull request #6855 from mruby/dependabot/github_actions/github-actions-dependencies-1f5b201d0b
build(deps): bump the github-actions-dependencies group with 2 updates
2026-05-23 07:58:14 +09:00
vobloeb c0d8abd0a3 test/bintest.rb: tokenize ENV['EMULATOR'] via Shellwords.split
`Command::CrossTestRunner#emulator` returns a shell-quoted string, which `Build#run_test` un-quotes via `sh`, but `CrossBuild#run_bintest` propagates verbatim through `ENV['EMULATOR']` to `test/bintest.rb`. The latter splices it into an Open3 exec-mode argv, where the literal `"` survives into `execve(2)` and the kernel returns `ENOENT`.

Switching to `Shellwords.split(ENV['EMULATOR'])` round-trips the quoted string correctly and also fixes multi-token emulator commands (e.g. `qemu-aarch64 -L /sysroot`), which currently end up concatenated into `argv[0]`.

Verified against mruby `3.3.0`, `3.4.0`, `4.0.0`, and `master`. Cross-build of mruby for `aarch64-unknown-linux-musl` (qemu-user 8.2.10): bintests went from 17/75 crashing → 100/100 passing.
2026-05-22 17:01:38 +00:00
dependabot[bot] 309b99efa5 build(deps): bump the github-actions-dependencies group with 2 updates
Bumps the github-actions-dependencies group with 2 updates: [github/codeql-action](https://github.com/github/codeql-action) and [j178/prek-action](https://github.com/j178/prek-action).


Updates `github/codeql-action` from 4.35.4 to 4.35.5
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/68bde559dea0fdcac2102bfdf6230c5f70eb485e...9e0d7b8d25671d64c341c19c0152d693099fb5ba)

Updates `j178/prek-action` from 2.0.3 to 2.0.4
- [Release notes](https://github.com/j178/prek-action/releases)
- [Commits](https://github.com/j178/prek-action/compare/6ad80277337ad479fe43bd70701c3f7f8aa74db3...bdca6f102f98e2b4c7029491a53dfd366469e33d)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.35.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-dependencies
- dependency-name: j178/prek-action
  dependency-version: 2.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-22 14:54:27 +00:00
Yukihiro "Matz" Matsumoto 66f438d8fe mruby-bin-debugger: return on OOM in mrb_debug_set_break_method
mrb_debug_set_break_method() freed set_class after mrdb_strdup() of
method_name failed but did not return. Execution continued into
alloc_breakpoint(), which on failure double-freed set_class, or on
success stored the dangling pointer in the breakpoint table for later
use-after-free. Return MRB_DEBUG_NOBUF immediately after the free.

mrdb_strdup uses mrb_malloc_simple which returns NULL on OOM (it does
not raise), so the NULL check is reachable in practice.

close #6851

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-22 07:08:26 +09:00
Yukihiro "Matz" Matsumoto 4507b4a633 mruby-bigint: gate mpz_mod's Barrett path on the algorithm's precondition
Barrett reduction requires x < 2^(2*bits(m)); the gate condition only
required x->sz >= y->sz + 2, which let x.sz reach 25 limbs against a
4-limb modulus. When the precondition is violated, mpz_barrett_reduce
silently truncates high limbs and returns garbage.

Integer#remainder, which routes through mpz_mod, was affected:
(3**500).remainder((2**100)+3) returned the wrong value. Integer#%
took the udiv path via mpz_mmod and was unaffected.

Add x->sz <= 2 * y->sz to the gate so out-of-range inputs fall
through to the general udiv path.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-22 07:08:26 +09:00
Yukihiro "Matz" Matsumoto d30ddd4296 Merge pull request #6852 from mruby/dependabot/pre_commit/pre-commit-hooks-613cf0bdf4
build(deps): bump https://github.com/rubocop/rubocop from v1.86.1 to 1.86.2 in the pre-commit-hooks group
2026-05-22 07:03:27 +09:00
dependabot[bot] a4e12dfc42 build(deps): bump https://github.com/rubocop/rubocop
Bumps the pre-commit-hooks group with 1 update: [https://github.com/rubocop/rubocop](https://github.com/rubocop/rubocop).


Updates `https://github.com/rubocop/rubocop` from v1.86.1 to 1.86.2
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.86.1...v1.86.2)

---
updated-dependencies:
- dependency-name: https://github.com/rubocop/rubocop
  dependency-version: 1.86.2
  dependency-type: direct:production
  dependency-group: pre-commit-hooks
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 16:28:35 +00:00
Yukihiro "Matz" Matsumoto 206b9f7477 mruby-bigint: pre-reduce base in mpz_powm_montgomery to satisfy REDC
REDC's input T must satisfy T < R*N; mpz_powm_montgomery() computed
T = base * R^2 without first reducing base modulo n. When base >= n,
T exceeds R*N and REDC silently truncates upper limbs, producing a
wrong base_mont and ultimately a wrong result.

Use mpz_mmod (general division path) to pre-reduce base mod n before
the multiplication by R^2.

Visible effect: (2**160).pow(2, (2**40)+1) returned 0 instead of 1.
A separate pre-existing issue in mpz_mod's Barrett path (broken
precondition check) means that path could return a wrong reduction
for large operands; mmod sidesteps that path entirely.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-21 17:07:51 +09:00
Yukihiro "Matz" Matsumoto 6ac2c3dc56 mruby-bigint: maintain canonical sn=0 when trim reduces sz to 0
mpz_t treats sn (sign) as the canonical "is zero" flag (zero_p(x) :=
(x)->sn == 0). When an arithmetic operation produces a value whose
limbs trim to zero size, sn must be reset to 0 to preserve the
invariant. Several call sites already enforced this locally (e.g.
mpz_sub line 616); make trim() responsible so every caller benefits.

Without this, an inconsistent zero bignum (sn!=0, sz=0) can flow into
mpz_sqr, miss the zero_p guard, and reach mpz_init_heap with hint=0
where mpn_zero(NULL, 0) invokes UB (memset() declares its first
argument nonnull). The trip survives at runtime on glibc but is
formally undefined behavior, flagged by UBSan via Integer#pow(b,e,m)
with specific operands.

close #6849

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-21 17:04:08 +09:00
Yukihiro "Matz" Matsumoto 72c117cb9e variable.c: guard assign_class_name against unresolvable symbol
mrb_sym_name_len() returns NULL when sym is 0, out of range, or
references a freed symbol slot. assign_class_name() indexed [0]
without checking, so malformed bytecode whose OP_CLASS operand
indexed past irep->slen could feed a bogus sym here and crash on
NULL[0]. Skip the class-naming side effect when the sym does not
resolve to a name.

close #6842

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-21 16:11:48 +09:00
Yukihiro "Matz" Matsumoto 465e634d48 mruby-regexp: fix SEGV on uninitialized Regexp's hash and ==
regexp_init() called re_compile() before setting @source / @flags
IVs, so a Regexp that survived a compile-time exception (e.g. picked
up via ObjectSpace.each_object after `Regexp.new("(")` raised) was
left with no @source. obj.hash then dereferenced nil through
mrb_str_hash() and crashed.

Set the IVs before re_compile(), and make regexp_hash / regexp_eql
defensive against a non-String @source so Regexp.allocate.hash also
behaves.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-21 12:32:40 +09:00
Yukihiro "Matz" Matsumoto 9a1c1eabf9 Merge pull request #6841 from dearblue/gc
Small refactor for `incremental_sweep_phase()`
2026-05-21 10:09:55 +09:00
Yukihiro "Matz" Matsumoto 54c8427df2 mruby-regexp: handle \b inside character class as backspace
Inside `[...]`, `\b` denotes U+0008 (backspace) -- the same as
MRI/Onigmo and PCRE.  parse_escape() was missing the case, so
the backslash was dropped and the bare letter `b` was inserted
into the class.  `[\b]` therefore matched every `b` instead of
backspace.

Add `case 'b': return '\b';` to parse_escape().  The function
is only reached from the character-class body and range
endpoints; the top-level dispatcher emits RE_WBOUND for `\b`
before falling through, so the word-boundary semantics outside
`[...]` are unchanged.

Reported by Sam Ruby in matz/spinel#632; same engine bug
affects both spinel and mruby.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-21 08:23:15 +09:00
dearblue df38160d82 Omit the check whether the typetag is MRB_TT_FREE after calling obj_free()
With commit cbc3dbedb4, freed objects will always be `MRB_TT_FREE`.
2026-05-20 21:15:54 +09:00
dearblue 2c0bccab66 Use else to skip the while loop in incremental_sweep_phase()
As a result, the variables can also be localized.
2026-05-20 21:15:54 +09:00
Yukihiro "Matz" Matsumoto 17d124b00d array.c (mrb_ary_splice): re-modify a after self-aset ary_dup
`a[range] = a` on a long-enough array tripped a heap-buffer-overflow
in value_move(). mrb_ary_splice's self-aset branch calls ary_dup(a)
to get an independent copy of the source elements, but ary_dup ->
ary_replace converts the source to shared as a copy-on-write
optimization when the length exceeds ARY_REPLACE_SHARED_MIN. After
that, a->as.heap.aux is reinterpreted as `shared` (the union member)
and ARY_CAPA(a) reads from the shared pointer's bits rather than
the real capacity. The expand-capa check below then silently mis-
sizes and value_move walks past the buffer.

Re-modify `a` immediately after ary_dup to un-share before the in-
place mutation. The buffer reads through `argv` (which now points
into the dup's storage) stay valid because ary_modify on a multi-
reference shared array allocates a fresh buffer for `a` and leaves
the original buffer owned by the dup.

Found via clusterfuzz mruby_fuzzer testcase 6525563811725312;
regression test covers a[3, 2] = a on a 31-element array (above
the ARY_REPLACE_SHARED_MIN=20 threshold).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 14:27:22 +09:00
Yukihiro "Matz" Matsumoto 3e2ce88eab hw-spi: wrap splat-bearing method signatures as code in README
After f91936b06e kept the splat notation unescaped, prettier
kept flagging the standalone `*` in the SPI#write / SPI#transfer
headings every CI run.  Wrap the two signatures in backticks so
prettier treats them as inline code (which they are), and the
`*data` form stays unescaped without further conflict.  Same
convention is already used in mruby-kernel-ext/README.md
(e.g. `### \`fail(*args)\``).

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-20 08:35:22 +09:00
Yukihiro "Matz" Matsumoto 7378be58cb Merge pull request #6838 from dearblue/gc_stat
Use `MRB_SYM()` in `gc_stat()`
2026-05-19 22:44:41 +09:00
Yukihiro "Matz" Matsumoto ebe4a7aed0 Merge pull request #6837 from dearblue/mrb_const_cache_clear
Avoid surrounding `#if` when using `mrb_const_cache_clear()`
2026-05-19 22:44:37 +09:00
dearblue 6d12f1f331 Use MRB_SYM() in gc_stat() 2026-05-19 22:28:15 +09:00
dearblue 6daa33c9a8 Avoid surrounding #if when using mrb_const_cache_clear()
`mrb_const_cache_clear()` is always available.
2026-05-19 21:22:27 +09:00
Yukihiro "Matz" Matsumoto ae29ab7db9 numeric.c: bounds-check float in Float#div before mrb_int cast
`Float#div(Integer)` cast its receiver to mrb_int unconditionally,
which is undefined behavior when the float is outside the
representable mrb_int range.  ASan reports the UB on inputs like
`5e+56.div(1)`.

Reported by ClusterFuzz testcase
clusterfuzz-testcase-minimized-mruby_fuzzer-5137605569347584.

Guard the cast with FIXABLE_FLOAT and route over-range receivers
to mrb_bint_div when MRB_USE_BIGINT is defined (matching
flo_rounding_int's pattern), or raise via mrb_int_overflow when
not.  Existing `(float in range).div(int)` semantics are
unchanged.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-19 12:15:38 +09:00
Yukihiro "Matz" Matsumoto 73255d3b70 mruby-numeric-ext: avoid signed overflow on Integer#gcd/lcm with MRB_INT_MIN
Negating MRB_INT_MIN (-2^63) is signed overflow (UB) because
2^63 does not fit in mrb_int.  Both `mrb_int_gcd` and `int_lcm`
took the absolute value via `if (x < 0) x = -x`, which trips on
MRB_INT_MIN.

Reported by ClusterFuzz testcase
clusterfuzz-testcase-minimized-mruby_fuzzer-5137605569347584.

* mrb_int_gcd: cast each input to mrb_uint before negating; the
  Euclidean reduction runs in unsigned.  The cast back at the
  end yields MRB_INT_MIN only when the mathematical gcd is 2^63
  (i.e., gcd(MIN, 0) or gcd(MIN, MIN)).
* int_gcd: detect the negative return value from mrb_int_gcd
  and raise via mrb_int_overflow, since the true result does
  not fit.
* int_lcm: short-circuit raise when either operand is
  MRB_INT_MIN (after the existing zero check), since the abs
  would overflow and the lcm with any non-zero operand could
  not fit anyway.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-19 12:05:33 +09:00
Yukihiro "Matz" Matsumoto db2845aae0 mruby-regexp: bounds-check group index in RE_BACKREF
The RE_BACKREF execution path read `captures[group * 2]` and
`captures[group * 2 + 1]` without verifying that the group
index fit in the allocated captures array. A pattern like
`/\1/` (no capture group, but a backreference to group 1) is
accepted by the compiler and lands in execution with `ncap = 2`
(only group 0 slots) and an instruction asking for group 1 --
a 4-byte read past the end of the allocation.

Reported by ClusterFuzz testcase
clusterfuzz-testcase-minimized-mruby_fuzzer-5474946829844480.

Add `if (group * 2 + 1 >= ncap) return FALSE;` ahead of the
captures access, mirroring the bounds guard already present in
RE_SAVE. The compiler's permissive `\<digit>` handling stays
unchanged; the runtime now treats a reference to a non-existent
group as a non-match rather than UB.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-19 10:40:45 +09:00
Yukihiro "Matz" Matsumoto 5bb4a15086 mruby-regexp: cap bt_match recursion depth
The backtracking engine recurses via C function calls at RE_SPLIT,
RE_SPLITNG, RE_SAVE, RE_LOOKAHEAD, RE_NEG_LOOKAHEAD, RE_LOOKBEHIND,
and RE_NEG_LOOKBEHIND.  Patterns like `(?=)+` make the engine
recurse without consuming input, exhausting the C stack and
triggering SIGSEGV long before MRB_REGEXP_STEP_LIMIT is reached
(each recursion charges only ~1 step, but each frame costs ~150
bytes of stack).

Reported by ClusterFuzz testcase
clusterfuzz-testcase-minimized-mruby_fuzzer-4653331195953152.

Add an integer recursion-depth counter passed alongside the step
counter, and abort the current branch with FALSE when it exceeds
MRB_REGEXP_RECURSION_LIMIT (default 1000, configurable like
STEP_LIMIT).  Legitimate patterns nest only a few levels;
pathological inputs bail without crashing the VM.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-19 10:34:39 +09:00
Yukihiro "Matz" Matsumoto 3e676af568 Merge pull request #6836 from hasumikin/fix/task
Fix mruby-task: keep join waiter wakeup under one IRQ critical section
2026-05-18 16:26:31 +09:00
HASUMI Hitoshi c4d205496d Fix mruby-task: keep join waiter wakeup under one IRQ critical section 2026-05-18 16:11:04 +09:00
Yukihiro "Matz" Matsumoto 1317a8bf5d Merge pull request #6835 from hasumikin/task-queue
Introduce Task::Queue
2026-05-18 15:38:16 +09:00
HASUMI Hitoshi a1a77e5acc Call task_check_scheduler_lock in queue_pop_try to avoid dead lock 2026-05-18 15:30:44 +09:00
HASUMI Hitoshi c9ae2524ee Cache Task::Error in task_error_class_ iniailized in mrb_init_task_queue() 2026-05-18 15:26:38 +09:00
HASUMI Hitoshi 458078f10e Introduce Task::Queue
- This patch implements `Task::Queue` mirroring `Thread::Queue` in CRuby.
- Producer/Consumer pattern is now possible with no polling

## No Top-level pollution
- No top-level `Queue` defined
- No `TaskError`. Instead, `Task::Error` happens when `Task::Queue#pop(true)` when empty
- No `ClosedQueueError`. `Task::Error` also happens when pushing to closed queue

## Future Work
- `Task::SizedQueue`
2026-05-18 14:23:42 +09:00
Yukihiro "Matz" Matsumoto d1bffb902e Merge pull request #6834 from Asmod4n/glib_hal
mruby-task GLib HAL
2026-05-17 23:31:33 +09:00
Asmod4n 17858cc5bd mruby-task GLib HAL
Lets mruby-task embed cleanly in any GLib-based event loop -- GTK,
libsoup, GStreamer, or anything else built on GMainContext. Tasks
become regular GSources, so the scheduler runs alongside whatever
else is on the loop without polling or busy-waiting.

Sleeping tasks cost zero CPU: the HAL parks until the next wakeup
deadline rather than ticking on a fixed cadence. Multiple mrb_states
on the same thread share one dispatcher and one ticker. Preemption,
Task.run, sleeper wakes, and foreign-loop integration all use the
same primitives, so embedders can mix Task.run with g_main_loop_run
freely.

ref mruby#6825
2026-05-17 13:44:20 +02:00
Yukihiro "Matz" Matsumoto aa25be69d7 Merge pull request #6833 from Asmod4n/patch-4
Improve wakeup tick condition check
2026-05-17 19:02:42 +09:00