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.
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>
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>
mrb_const_set may invoke const_added via mrb_funcall_argv, which
re-enters the VM and can reallocate cibase. This invalidates the
local ci pointer (and thus the regs macro that expands to ci->stack),
causing a use-after-free on the next opcode dispatch.
OP_SETMCNST and the OP_GET* / OP_*IDX opcodes already follow this
pattern; align OP_SETCONST with them.
Reported by OSS-Fuzz (clusterfuzz testcase 5886006653157376).
Co-authored-by: Claude <noreply@anthropic.com>
heapify() and heap_delete_root() call mrb_gc_protect() to guard a
C-local mrb_value across potential GC points inside sort_cmp().
They were missing the matching mrb_gc_arena_save/restore pair, so
each call leaked one arena slot. For O(n log n) heapify calls under
MRB_GC_FIXED_ARENA this overflows the 100-slot arena and raises
NoMemoryError, e.g. during Array#repeated_permutation tests.
Wrap each function body with mrb_gc_arena_save/restore, matching
the pattern already used in insertion_sort() above.
Co-authored-by: Claude <noreply@anthropic.com>
Remove early return of hardcoded 0 for zero floats. Instead, normalize
-0.0 to +0.0 and pass through mrb_byte_hash() for better distribution.
The previous second condition (f == -0.0) was dead code since IEEE 754
-0.0 == 0.0 is true.
Co-authored-by: Claude <noreply@anthropic.com>
Use FNV-1a (xor-then-multiply) instead of FNV-1 for better avalanche
in byte hashing. Strengthen the hash finalizer in mrb_obj_hash_code()
with multiply-xorshift to improve distribution for integer and symbol
keys with power-of-two table sizes.
Co-authored-by: Claude <noreply@anthropic.com>
Replace the inline switch with a separate fast_fmt_ok() function that
maps each format character to a validity code (0=invalid, 1=arg spec,
2=separator). Modern compilers generally lower this to a jump table,
so the per-character cost remains effectively O(1).
Keeping this as a switch (instead of a C99 array-index designator
lookup table) also lets the file compile cleanly as C++.
Co-authored-by: Claude <noreply@anthropic.com>
Skip the two-pass format scanning when the format string contains
only simple specifiers (o, S, i, n, z, b, f, A, H, c, s, a) with
optional '|' separator. Validates all specifiers before consuming
va_list to ensure safe fallback to the slow path.
Covers ~50% of all mrb_get_args call sites in the codebase (175
of 353). Reduces per-call argument parsing overhead by ~30-40%.
Co-authored-by: Claude <noreply@anthropic.com>
CI_PROC_SET: split NULL/non-NULL proc paths so the compiler can
eliminate the CFUNC/ALIAS checks when proc is a compile-time NULL
(8 of 11 cipush call sites).
cipop: add fast path for the common case where no env and no blk
are set. skips ci_env_set, orphan check, and env_unshare entirely.
most simple method calls (no blocks, no closures) take this path.
Co-authored-by: Claude <noreply@anthropic.com>
split mrb_obj_alloc() into type-validation wrapper and allocation
core (mrb_obj_alloc_core). internal callers (mrb_proc_new,
mrb_env_new) use the core directly, skipping 15+ lines of type
validation per allocation.
most impactful for workloads with heavy Proc/Env allocation
(lambda calculus, block-intensive code).
Co-authored-by: Claude <noreply@anthropic.com>
when all elements are plain String (not subclass) and no block is
given, use specialized sort that calls mrb_str_cmp() directly,
bypassing sort_cmp overhead (GC arena, type dispatch, array
modification check).
includes subclass check to ensure String#<=> is not overridden.
Co-authored-by: Claude <noreply@anthropic.com>
when all elements are integers and no block is given, use
specialized heapify/insertion_sort that compare mrb_int values
directly, bypassing sort_cmp entirely. this eliminates per-comparison
overhead of GC arena save/restore, type checking, and array
modification checks.
the pre-scan to detect all-integer arrays is O(n), negligible
compared to O(n log n) sort. non-integer and block sorts are
unaffected.
Co-authored-by: Claude <noreply@anthropic.com>
two improvements to Array#sort!'s heap sort:
1. hole-style sift-down: save root value, move larger children up
one at a time, write saved value once at the end. reduces
assignments from 3 per level (swap) to 1 per level (move).
2. Floyd's bottom-up heap deletion: during extraction phase, sift
the hole down to a leaf using only child-child comparisons
(~1 comparison per level), then sift up to find the correct
position. this reduces average comparisons from ~2 log n to
~log n per extraction, nearly halving the total comparison
count for the sort.
both changes preserve O(n log n) worst case and O(1) extra space.
Co-authored-by: Claude <noreply@anthropic.com>
when dynamic symbol count reaches MRB_SYMBOL_MAX, run a mark-sweep
pass over all live objects to identify referenced symbols. sweep
unreferenced dynamic symbols, freeing their individually-allocated
string data and marking symtbl slots as tombstones.
mark phase traverses:
- all heap objects (method tables, IV tables, arrays, hashes, envs)
- VM stack values (MRB_TT_SYMBOL)
- call stack method IDs (ci->mid)
- root and current context
after sweep, rebuild hash table to maintain valid collision chains.
this completes the A+ symbol GC plan: the limit acts as a GC
trigger rather than a hard cap. unreferenced DoS symbols are
reclaimed, allowing legitimate code to continue.
Co-authored-by: Claude <noreply@anthropic.com>
dynamic symbols (created via to_sym, send, etc.) now use
mrb_malloc() instead of sym_pool_alloc(). this makes them
individually freeable by future symbol GC.
static symbols (presym, mrb_intern_static, literals) continue
to use the pool allocator for compact storage.
Co-authored-by: Claude <noreply@anthropic.com>
track dynamic (runtime-created) symbols separately from presyms,
inline symbols, and static C API symbols. raise RuntimeError when
the dynamic symbol count exceeds MRB_SYMBOL_MAX (default 4096).
this prevents DoS attacks via unbounded symbol creation (e.g.
"str".to_sym in a loop). presyms and inline symbols are not
counted toward the limit.
infrastructure for future symbol GC: sym_flags array tracks
per-symbol metadata (SYM_FL_DYNAMIC flag).
Co-authored-by: Claude <noreply@anthropic.com>
replace four repeated `mrb_free(mrb, bin); return MRB_DUMP_WRITE_FAULT`
sequences with a single goto-based cleanup path.
Co-authored-by: Claude <noreply@anthropic.com>
mrb_include_module() and mrb_prepend_module() did not invalidate
the constant cache. stale cache entries caused incorrect constant
resolution after include changed the ancestor chain.
Co-authored-by: Claude <noreply@anthropic.com>
mrb_vm_define_module() and mrb_vm_define_class() incorrectly
reopened modules/classes accessible through include rather than
creating new ones. CRuby only reopens modules directly defined
on the outer scope.
the internal define_module()/define_class() use
mrb_const_defined_at() which walks ancestors for Object class.
bypass them and create modules/classes directly in the VM path.
Co-authored-by: Claude <noreply@anthropic.com>
three functions differed only in the trailing character check ('=',
'?', '!'). replace with a single parameterized function.
Co-authored-by: Claude <noreply@anthropic.com>
both opcodes share identical proc dispatch logic (alias resolution,
callinfo setup, cfunc/irep branching). the only difference is how
nargs is computed (ci_bidx vs operand b).
Co-authored-by: Claude <noreply@anthropic.com>
Decrement gc_debt by the actual number of objects processed
instead of the fixed GC_STEP_SIZE. This makes step_ratio
directly affect debt repayment: larger steps repay more debt,
naturally reducing GC invocation frequency.
Co-authored-by: Claude <noreply@anthropic.com>
Expose gc_debt directly as :debt in GC.stat without sign negation.
The debt model has no threshold ceiling, so :threshold was a
misleading name. Negative debt means credit, positive means GC
is behind on collection work.
Co-authored-by: Claude <noreply@anthropic.com>
Use -gc_debt as the :threshold key in GC.stat for familiarity.
Positive means credit remaining, negative means GC is overdue.
Co-authored-by: Claude <noreply@anthropic.com>
Replace the threshold-based GC trigger (gc->threshold vs gc->live)
with a debt model (gc->gc_debt). Each allocation increments debt;
each GC step decrements by GC_STEP_SIZE. When a cycle completes,
credit is proportional to live_after_mark * interval_ratio, giving
a natural feedback loop that adapts to allocation rate.
Co-authored-by: Claude <noreply@anthropic.com>
GC.step_limit caps the per-step work in incremental GC,
enabling more predictable pause times for real-time use.
GC.malloc_threshold triggers GC based on allocation bytes,
addressing memory pressure from large buffers.
Both default to 0 (disabled), preserving existing behavior.
Co-authored-by: Claude <noreply@anthropic.com>
Leaf types (String, Integer, BigInt, Complex, CPTR) have no children
besides their class pointer. Mark them black immediately in
mrb_gc_mark() instead of pushing to the gray stack, reducing gray
stack pressure and overflow frequency.
Co-authored-by: Claude <noreply@anthropic.com>
Return a Hash with GC statistics: live, threshold, state,
generational, full. When MRB_GC_STATS is defined, also includes
total, minor, major counters.
Co-authored-by: Claude <noreply@anthropic.com>
Add gc_total_count, minor_gc_count, major_gc_count (uint32_t) to
mrb_gc, guarded by MRB_GC_STATS. Zero cost when disabled.
Co-authored-by: Claude <noreply@anthropic.com>
Phase 2 of opcode handler extraction. these opcodes use
L_SEND_SYM/L_SENDB_SYM fallback for generic method dispatch when
the fast path (Array/Hash/String/Integer/Float) does not apply.
add VM_SEND_SYM and VM_SENDB_SYM return codes. move TYPES2 macro
to file scope for use by vm_op_div.
Co-authored-by: Claude <noreply@anthropic.com>
extract the three largest self-contained opcode handlers from the
mrb_vm_exec() dispatch loop into static functions. add
__attribute__((flatten)) to mrb_vm_exec() so that the compiler
inlines them back, producing identical binary output while keeping
the source clean.
Co-authored-by: Claude <noreply@anthropic.com>
Use uscale-based shortest() to compute the minimal decimal string
that uniquely identifies each double. This guarantees perfect
round-trip (parse(to_s(x)) == x) while keeping output concise
(e.g. 0.1 prints as "0.1", not "0.10000000000000001").
Co-authored-by: Claude <noreply@anthropic.com>
Replace separate float formatting (fmt_fp.c) and parsing (readfloat.c)
implementations with a unified fp_uscale.c using 128-bit unrounded
scaling. Both mrb_format_float() and mrb_read_float() now share a
single pow10 table and uscale() primitive for decimal/binary conversion.
This fixes subnormal parsing accuracy (old code returned 0.0 for the
smallest subnormals) and corrects %.2f rounding for values like
12345.125. Table size grows from ~5KB to ~11KB in .rodata.
Co-authored-by: Claude <noreply@anthropic.com>
Remove the per-entry generation field and per-state generation
counter. Invalidation now clears entries directly, removing one
comparison from every OP_GETCONST hot path.
Co-authored-by: Claude <noreply@anthropic.com>
Right-shift class pointer by 4 before hashing to remove
always-zero alignment bits, improving hash distribution.
Organize 256 cache entries as 128 sets x 2 ways to reduce
conflict misses when multiple methods share a hash bucket.
Co-authored-by: Claude <noreply@anthropic.com>
Cache OP_GETCONST results in a global direct-mapped cache (64 entries)
keyed by (irep, sym). Invalidate all entries via a generation counter
bumped on mrb_const_set(), mrb_const_remove(), and
mrb_define_const_id(). ~10% faster on constant-heavy code; disabled
with MRB_NO_CONST_CACHE.
Co-authored-by: Claude <noreply@anthropic.com>
The previous code computed `frac_part * pow10_negative[n]`, where
pow10_negative[n] is already a rounded approximation of 10^-n (since
10^-n is not exactly representable in binary). The multiplication then
adds another rounding step, leaving up to ~1 ulp of error.
Dividing `frac_part` by `pow10_positive[n]` is exact for n <= 22 (the
range where 10^n fits exactly in a double), so the division is the
only rounding and the result is correctly rounded. For example,
"0.3".to_f now matches the 0.3 literal's bit pattern (and libc strtod).
`mrb_class_get_id()` may call the `#const_missing` method.
Therefore, if the `mesg` string originates from a string object, it may reference an invalid address.
And since `errno` might also change during the call to `#const_missing`, save this as well beforehand.
Also, while `mrb_class_defined_id()` does not currently call the `#const_defined?` method, it is unclear whether this will remain the case in the future.