With this PR, I can remove the original task.c in picoruby/picoruby and future development will be much easier.
## Add
### General
- C API functions exported with MRB_API for external integration:
- mrb_execute_proc_synchronously() for synchronous proc execution
- Task control APIs (mrb_create_task, mrb_suspend_task, mrb_resume_task, mrb_terminate_task, mrb_stop_task, mrb_task_value, mrb_task_status)
- Task context management APIs for picoruby-sandbox (mrb_task_init_context, mrb_task_reset_context, mrb_task_proc_set)
- Task.tick class method to get current tick count
- Comprehensive C API documentation with WASM integration examples
### For PicoRuby.wasm
- WASM/Emscripten support: Disable SIGALRM timer when __EMSCRIPTEN__ is defined, as JavaScript handles tick calls via setInterval
- Scheduler lock mechanism to prevent asynchronous task operations during synchronous execution (scheduler_lock counter in mrb_task_state)
- mrb_task_run_once() for single-step execution (event loop integration)
## Fix
### task.c
- Replace MRB_FIBER_TERMINATED with MRB_TASK_STOPPED just for clarity
- Allow suspending DORMANT and WAITING tasks in mrb_task_suspend (See comment in the source)
- Task context initialization by removing dummy callinfo push/pop
*NOTE*
With the dummy callinfo code that I deleted, IRB in PicoRuby ended SEGV.
If that code is mandatory, we need to discuss how to solve my problem.
### vm.c
- Handle MRB_TASK_CREATED status in VM's NORMAL_RETURN phase to properly stop tasks
----
These changes are necessary to make PicoRuby work.
Nevertheless, even with this patch, MicroRuby for Raspberry Pi Pico 2 is still unstable.
I would like to merge this PR anyway to make development easier by involving the PicoRuby community.
Add fast path for multiplying by powers of 2 (2^n). Uses left shift
instead of Karatsuba multiplication: x * 2^n = x << n.
This optimizes "mostly-zero" patterns common in fuzzing tests, where
numbers like 2^2097150 (single bit set) would otherwise trigger slow
Karatsuba multiplication.
Co-authored-by: Claude <noreply@anthropic.com>
Add fast path for multiplying numbers of form 2^n - 1 (all bits set).
Uses algebraic identities:
- (2^n - 1) * (2^m - 1) = 2^(n+m) - 2^n - 2^m + 1
- (2^n - 1) * y = (y << n) - y
These are O(n) operations instead of O(n^1.585) for Karatsuba.
Fuzzing test cases using all-ones patterns now complete in 0.01s
instead of 13+ seconds.
Also raises KARATSUBA_THRESHOLD from 8 to 32 for ~32% speedup
on general large number multiplication.
Co-authored-by: Claude <noreply@anthropic.com>
Reduces recursion overhead for large number multiplication.
Benchmarks show ~32% speedup for million-bit operands.
Co-authored-by: Claude <noreply@anthropic.com>
Add MRB_BIGINT_BIT_LIMIT (1 billion bits / 128MB) to prevent
unreasonably large allocations when left-shifting by huge amounts.
Raises RangeError instead of attempting multi-GB allocations.
Co-authored-by: Claude <noreply@anthropic.com>
mpz_and, mpz_or, and mpz_xor were not calling trim() on their results,
causing inflated size values with trailing zero limbs. This led to
incorrect comparisons in ucmp() and caused udiv() to take wrong code
paths, resulting in memory leaks when exceptions occurred.
Also added defensive overflow checks in mpz_init_heap and udiv.
Co-authored-by: Claude <noreply@anthropic.com>
when converting a shared/static string (IREP_TT_SSTR) to heap-allocated
(IREP_TT_STR), copy the original content to the new buffer.
previously, the original content was lost when allocating new memory,
leaving the first bytes uninitialized. this caused find_pool_str() to
read uninitialized memory via memcmp() when searching for duplicate
strings.
reported by OSS-Fuzz.
Co-authored-by: Claude <noreply@anthropic.com>
When defining a method with a required keyword argument without
parentheses, mruby incorrectly parsed the next line as the default
value:
def foo arg:
123
end
Was parsed as: def foo(arg: 123); end (optional kwarg, empty body)
Should be: def foo(arg:); 123; end (required kwarg, body returns 123)
The fix sets EXPR_ARG lexer state after parsing f_label, making
newlines significant. This prevents the parser from consuming
expressions across line boundaries as default values for keyword
arguments.
Also fixes a pre-existing bug in f_label where tNUMPARAM (type <num>)
was implicitly assigned to $$ (type <id>) without conversion. Now
explicitly uses intern_numparam() to convert numbered parameters to
symbols.
Fixes https://github.com/mruby/mruby/issues/6268
rename mrb_alloca() to mrb_temp_alloc() for clearer naming - the new name
better describes its purpose as GC-managed temporary allocation. keep
mrb_alloca() as a macro alias for backward compatibility.
apply mrb_temp_alloc() to fix potential memory leaks in:
- mruby-strftime: if mrb_str_cat() raises, allocated buffers now cleaned by GC
- mruby-io File.readlink: if mrb_str_new() raises, buffer now cleaned by GC
Co-authored-by: Claude <noreply@anthropic.com>
when a Set contains itself (directly or indirectly), computing its hash
would cause infinite recursion leading to SystemStackError. the exception
during khash rebuild leaked memory.
add recursion detection flag to Set#hash that returns 0 for recursive
references, similar to Ruby's behavior.
Co-authored-by: Claude <noreply@anthropic.com>
prevent resource exhaustion when computing power with extremely large
exponents (e.g., 81.pow(51742871469327219)). the check estimates the
result size and raises RangeError if it would exceed 1 million bits.
Co-authored-by: Claude <noreply@anthropic.com>
Shifting 1 left by MRB_INT_BIT-1 (e.g., 63 on 64-bit) bits into the sign
bit is undefined behavior. Change the overflow check from >= MRB_INT_BIT
to >= MRB_INT_BIT-1 to prevent this.
Co-authored-by: Claude <noreply@anthropic.com>
When year value is close to MRB_INT_MIN, subtracting TM_YEAR_BASE (1900)
causes signed integer overflow. Add underflow check before the subtraction.
Co-authored-by: Claude <noreply@anthropic.com>
The local flock() function for Windows is now dead code since the
HAL refactoring. The Windows implementation is in hal-win-io which
provides mrb_hal_io_flock().
Fixes warning: 'flock' defined but not used [-Wunused-function]
Co-authored-by: Claude <noreply@anthropic.com>
Replace mrb_assert with mrb_ensure_*_type for VM opcodes that require
specific types:
- OP_ARYCAT: mrb_ensure_array_type
- OP_ARYPUSH: mrb_ensure_array_type
- OP_ASET: mrb_ensure_array_type (also fixed: was checking wrong register)
- OP_INTERN: mrb_ensure_string_type
- OP_HASHCAT: mrb_ensure_hash_type
These checks catch codegen bugs with clear error messages in both
debug and release builds.
Co-authored-by: Claude <noreply@anthropic.com>
Replace mrb_assert with mrb_ensure_string_type to catch codegen bugs
even in release builds. This prevents null-dereference crashes when
OP_STRCAT receives a non-string first operand due to compiler bugs.
Consistent with OP_HASH which uses mrb_ensure_hash_type for similar
type safety.
Co-authored-by: Claude <noreply@anthropic.com>
After pattern matching code generation, the sp (stack pointer) must
be restored to match the success path value. The failure path (after
RAISEIF) left sp in a different state, causing incorrect register
allocation in subsequent code like string interpolation.
This caused OP_STRCAT to use the wrong register, leading to
null-dereference when trying to modify a non-string value as a string.
Co-authored-by: Claude <noreply@anthropic.com>
The key variable in insertion_sort temporarily holds an array element
that's been removed from its slot during the sorting process. When
sort_cmp yields to a block that triggers GC, key wasn't protected
and could be collected.
Use arena save/restore around the loop to avoid arena overflow for
large arrays.
Test case from oss-fuzz: sort! with block containing rescue.
Co-authored-by: Claude <noreply@anthropic.com>
The => pattern matching codegen was doing push() after RAISEIF, even though
RAISEIF never returns. This caused sp to be off by 1 when success and failure
paths joined, resulting in wrong register allocation for subsequent operations.
For string interpolation like "#{ expr => pattern rescue body }", the base
string would be at R2 but STRCAT would incorrectly use R3, causing memory
corruption and crashes.
Test case: %{#{.=>.,. rescue def .()end}} (from oss-fuzz)
Co-authored-by: Claude <noreply@anthropic.com>
rational_new_b() expects both arguments to be bigints, but rational_new_f()
was passing an integer value for the numerator when the exponent was negative.
This caused a segfault in mrb_bint_reduce() which called RBIGINT() on the
integer value.
Test case: 5r**-92 (from oss-fuzz)
Co-authored-by: Claude <noreply@anthropic.com>
mruby-sleep: declare slp_tm before #ifdef _WIN32 block to fix
undeclared variable error in non-Windows branch.
hal-posix-socket: use #if defined(HAVE_SA_LEN) && HAVE_SA_LEN instead
of #ifdef HAVE_SA_LEN, since mruby-socket defines HAVE_SA_LEN to 0 on
non-BSD platforms.
Co-authored-by: Claude <noreply@anthropic.com>
Gems like mruby-task add preprocessor defines (MRB_USE_TASK_SCHEDULER)
that affect mrb_state structure. The amalgamation generator now detects
these defines from the build configuration and adds them at the top of
mruby.h before struct definitions are encountered.
Supported define patterns: MRB_USE_*, MRB_UTF8_*, HAVE_MRUBY_*
Co-authored-by: Claude <noreply@anthropic.com>
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>