9289 Commits
Author SHA1 Message Date
nagachika 72d9d4b935 merge revision(s) 670f6c1541: [Backport #22479]
[PATCH] Keep the symlink target alive while realpath_rec reads it

	realpath_rec reads the names of a symlink target through a raw pointer
	into the string returned by rb_readlink, while it calls itself to resolve
	those names. RB_GC_GUARD(link_orig) keeps that string alive only for an
	absolute target. For a relative target, link_orig is Qnil and nothing
	uses `link` after the recursive call, so GC can free the string while the
	later names are still being read.

	File.realdirpath then returns a wrong path: the unresolved symlink path,
	or a path with NUL bytes, which makes Pathname.new raise "pathname
	contains null byte". CocoaPods calls Pathname#realdirpath for every
	source file, and pnpm installs packages behind relative symlinks, so
	`pod install` fails at random in such projects.

	File.realpath is not affected on platforms with realpath(3), because only
	RB_REALPATH_DIR uses realpath_rec there.

	Fixes [Bug #22479]
2026-10-28 21:31:46 +09:00
nagachika 1efcec846a merge revision(s) 3a55e5c6d3:
[PATCH] Discard unreachable exception table entries

	Dead labels can leave invalid catch entries that make ensure clauses
	run for code that cannot be reached.
2026-10-25 22:50:27 +09:00
Sethu d64496b422 [Bug #22384] Fix memory leak on compile error in a later branch of an alternation
(cherry picked from commit bf937bfa16)
2026-10-23 11:43:06 +09:00
nagachika 8d4a42139b merge revision(s) 493d26dcde:
[PATCH] Reject overflowed repeat/null-check IDs in huge regexps (#16801)

	Add explicit errors for excessive range-repeat and null-check IDs;
	map new errors in regerror; add regression tests for oversized generated
	patterns.
2026-10-22 21:55:13 +09:00
Douglas Eichelberger 56c3e7ea7b Join getaddrinfo and getnameinfo threads before fork
A getaddrinfo thread calls getaddrinfo(3) inside rb_thread_prevent_fork,
but on glibc its thread exit takes the resolver configuration lock again
after the thread has released the fork lock. A fork that lands then
leaves the child with that lock held, and every later name lookup in the
child blocks forever. getnameinfo(3) attaches the same resolver state,
even for a name in /etc/hosts, and its thread ran outside
rb_thread_prevent_fork altogether.

Make the lookup threads joinable and have fork join any that may still
be exiting. As a lookup thread finishes, still inside
rb_thread_prevent_fork, it swaps itself into a single slot with an
atomic exchange and joins the thread it displaced, which has usually
exited by then, and rb_thread_acquire_fork_lock joins whatever is left
in the slot. Once lookups settle, at most one finished thread is left
unjoined. The slot holds pthread_t as a pointer, with NULL for empty,
which assumes pthread_t is scalar and never 0.

getnameinfo threads now run within rb_thread_prevent_fork and register
as they exit, as getaddrinfo threads do, and where getnameinfo runs
without a helper thread it is called within rb_thread_prevent_fork too.
So fork also waits for a reverse lookup in progress, as it already did
for a forward one. raddrinfo_pthread_create now always creates joinable
threads, which drops its pthread_detach fallback for platforms without
pthread_attr_setdetachstate.

The slot is kept across fork, so every fork path that keeps running
Ruby code has to take the fork lock. On the BSDs, Process.daemon forks
with daemon(3) and did not take it, so the daemon would inherit the
registered thread, and its next fork would abort in pthread_join with
ESRCH. Take and release the fork lock around daemon(3) as rb_fork_ruby
does. daemon(3) can fail in the child as well as in the parent, so
compare pids to tell which process returned.

[Bug #22406]

(cherry picked from commit 44f7c6b00d)
2026-10-17 14:24:19 +09:00
Randy Stauner 79214e7c6e [Backport #22415] Restore ec->errinfo after handling interrupts
Interrupts are checked at arbitrary points, including while a jump
(break/next/redo/return/raise/throw) is being propagated, and the payload
of that jump lives in ec->errinfo.

Handling an interrupt can run Ruby code: rb_signal_exec() runs a trap
handler and threadptr_interrupt_exec_exec() runs interrupt_exec tasks.
Neither preserves ec->errinfo, and any rescue or ensure clause that code
enters sets it to nil.

Thread.handle_interrupt checks for interrupts after its block returns but
before re-raising the jump that left the block.  If a signal handler runs
in that window the throw data is replaced with nil and the VM dereferences
nil as a vm_throw_data:

    THROW_DATA_CATCH_FRAME (obj=0x4) at vm_insnhelper.h:217
    vm_exec_handle_exception (ec=..., errinfo=4) at vm.c:2880
    vm_exec_loop / rb_vm_exec at vm.c:2829 / vm.c:2812
    invoke_iseq_block_from_c / rb_yield / rb_ary_each

rb_postponed_job_flush() and rb_exec_event_hooks() already save and restore
ec->errinfo around the Ruby code they run; do the same for the rest of
rb_threadptr_execute_interrupts().  The paths that set errinfo on purpose
(rb_exc_raise, rb_threadptr_to_kill) jump out of the function and so do not
reach the restore.
2026-10-17 14:16:01 +09:00
Erik Berlin 26a4d835f7 Fix Range#include? of an empty range of single characters
A range of single character Strings told that it includes its end
even when it is empty, since the end was not compared with the begin.

    ("z".."a").to_a          #=> []
    ("z".."a").include?("a") #=> true

(cherry picked from commit 1bf9130943)
2026-10-17 13:21:36 +09:00
Hiroya Fujinami 8382f95cfd Prevent double free for too big repetition quantifiers (#13332)
Prevent double free for too big repetition quantifiers

The previous implementation calls `free(node)` twice (on parsing and compiling a
regexp) when it has an error, so it leads to a double-free issue. This
commit enforces `free(node)` once by introducing a temporal pointer to hold
parsing nodes.

(cherry picked from commit 35000ac2ed)
2026-10-12 17:22:46 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 f800509896 Avoid overflow in the quantifier expansion size check
Nested bounded repeats let the int product tlen * qn->lower overflow, so
the check that keeps the compiler from unrolling a large repeat could pass
for an enormous one, hanging the compile or emitting wrapped jump offsets.
Compare against the limit by division instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:21:24 +09:00
nagachika f5cef120e9 merge revision(s) 2d08b5f42f: [Backport #22423]
[PATCH] [Bug #22423] Preserve encoding with `String` capacity
2026-10-12 14:22:20 +09:00
nagachika a1c96b6b0f merge revision(s) 3f6143715a: [Backport #22326]
[PATCH] Fix buffer overflow in Array#values_at

	Array#values_at can execute arbitrary code during execution which may
	modify the source array. This can cause a buffer overflow if the new length
	of the array has decreased. The following script crashes:

	    a = (0..100_000).to_a
	    obj = Object.new
	    obj.define_singleton_method(:begin) do
	      a.clear
	      0
	    end
	    obj.define_singleton_method(:end) { 10_000 }
	    obj.define_singleton_method(:exclude_end?) { false }
	    puts a.values_at(obj).length
2026-10-12 14:20:29 +09:00
nagachika 357795cca8 merge revision(s) 9267f6fba0, 3ea4bc3f79, badce4d079: [Backport #22388]
[PATCH] [Bug #22388] Fix use-after-free in `Enumerator::Lazy#flat_map`

	[PATCH] [Bug #22388] Fix use-after-free in `Set#&`

	[PATCH] Add a more defensive GC guard
2026-10-12 13:52:04 +09:00
nagachika f7e692b6cd merge revision(s) c861619e89, 3ac822c7e1: [Backport #22388]
[PATCH] [Bug #22388] Fix use-after-free in some `Enumerable` methods

	[PATCH] [Bug #22388] Fix use-after-free in `Enumerable#sum`
2026-10-12 13:34:44 +09:00
nagachika 8dd3d5b5e5 merge revision(s) ec3b6b12e7, f021cc2f13, 020ccecec3: [Backport #22284]
[PATCH] [Bug #22284] Fix curry with a non-Fixnum arity on LLP64

	FIX2INT does not call to_int where int and long have the same size, so `method(:puts).curry(1r)` waited for a garbage number of arguments on Windows. Convert the arity once and keep the result as a Fixnum, which also limits the to_int call to one.

	[PATCH] [Bug #22284] Let NUM2INT handle Fixnum arity of curry

	NUM2INT already takes the Fixnum path by itself.

	[PATCH] [Bug #22284] Test curry with an arity that responds to to_int

	It also counts the to_int calls, so it fails on LP64 platforms too, where the old code converted the arity again on every call.
2026-10-04 19:29:33 +09:00
nagachika 0a33ab92c0 merge revision(s) 227006291d: [Backport #22342]
[PATCH] Fix IO#gets ignoring the limit argument

	IO#gets first searches for separator candidates by looking for the last
	byte of the record separator, and then checks whether it is an actual
	match.

	If a candidate is found at limit-th byte of the stream but is rejected
	because it does not start at a character boundary, the limit check is
	incorrectly skipped.

	[Bug #22342]
2026-09-26 21:38:21 +09:00
nagachika 93d1c2d7c3 merge revision(s) ee786598f1, 54f254aa41, 15dbb08ba8, 249de200d5: [Backport #22338]
[PATCH] [Bug] win32: create directory symlinks for relative directory targets

	File.symlink decided whether to create a directory symlink by calling
	GetFileAttributesW on the target as given, which resolves a relative
	target against the current directory instead of the link's directory.
	When the two differ, a relative target pointing at a directory became a
	file symlink, so Dir operations on it failed. Resolve relative targets
	against the link's directory before testing for a directory.

	Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

	[PATCH] win32: use MEMCPY for overflow-safe copy in w32_symlink

	MEMCPY checks the size calculation for overflow, unlike a raw memcpy.

	Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

	[PATCH] [Bug] win32: do not resolve a root-relative symlink target against the link dir

	A target starting with a single backslash is relative to the root of the
	current drive, not to the link's directory.  Treat any backslash-leading
	target (root-relative or UNC) as non-relative when deciding the directory
	flag; otherwise w32_symlink builds a malformed path and fails to detect
	the directory attribute.

	Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

	[PATCH] win32: rename the symlink target flag to independent

	The flag also covers drive-relative and root-relative targets, which are
	not absolute paths.  "independent" describes that the target is not
	interpreted relative to the link's directory.

	Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-23 13:30:55 +09:00
nagachika 41e214d710 merge revision(s) d87ffb271a: [Backport #22335]
[PATCH] [Bug #22335] Use identity hashes for keyword argument tracking

	With more than 31 keyword arguments, tracking unevaluated defaults
	uses an integer-keyed Hash whose collisions can invoke methods when
	`Integer#==` is refined or redefined, corrupting argument locals above
	`cfp->sp`.  Use identity hashes to prevent these calls during argument
	setup and `checkkeyword` execution.

	Making `rb_any_cmp` compare special constants directly would also
	bypass redefined `eql?` methods in ordinary hashes.  Limit identity
	comparison to the internal keyword tracking hashes to preserve that
	behavior for now.
2026-09-23 13:27:15 +09:00
nagachika 7bbe8e8800 merge revision(s) ad23fd4a51: [Backport #22340]
[PATCH] [Bug #22340] Fix stat on symbolic-link loops on Windows

	Preserve `ELOOP` instead of hiding symbolic-link resolution errors
	with the file-enumeration fallback, so existence checks return false.
2026-09-23 13:24:07 +09:00
nagachika 75309143a8 merge revision(s) cb30288162, c914aeb622: [Backport #22330]
[PATCH] Fix memory leak of transcoding when fallback raises

	When the fallback function in transcode_loop raises, it will leak the memory
	in rb_econv_t. The following script reproduces the leak:

	    10.times do
	      100_000.times do
	        "\ufffd".encode(Encoding::US_ASCII, fallback: proc { raise })
	      rescue
	      end

	      puts `ps -o rss= -p #{$$}`
	    end

	Before:

	    451196
	    889980
	    1328508
	    1767676
	    2206460
	    2645372
	    3083900
	    3522428
	    3960956
	    4399484

	After:

	    12508
	    12636
	    12892
	    12892
	    13148
	    13404
	    13532
	    13788
	    13916
	    13916

	[PATCH] Fix use-after-free in String#encode when string modified
	MIME-Version: 1.0
	Content-Type: text/plain; charset=UTF-8
	Content-Transfer-Encoding: 8bit

	[Bug #22330]

	The following script demonstrates a use-afer-free where we see corruption:

	    s = "あ" * 10_000
	    s.encode("US-ASCII", fallback: proc { |c| s.clear; "?" })

	Raises may different errors such as:

	    "\xCB" followed by "\xF5" on UTF-8 (Encoding::InvalidByteSequenceError)
	    "\xF4" followed by "r" on UTF-8 (Encoding::InvalidByteSequenceError)
	    "\x8B" on UTF-8 (Encoding::InvalidByteSequenceError)
2026-09-23 13:23:20 +09:00
nagachika 5721bcee5a merge revision(s) 1007084edc: [Backport #9932]
[PATCH] Fix out-of-bounds in Array#permutation when array modified

	Calling to_int on the count may run Ruby code that modifies the array.
	This can cause an out-of-bounds in Array#permutation. For example, the
	following code crashes:

	    ary = (1..1_000).to_a
	    obj = Object.new
	    obj.define_singleton_method(:to_int) do
	      ary.replace(Array.new(5) { :x })
	      2
	    end
	    ary.permutation(obj) { }
2026-09-22 18:39:29 +09:00
nagachika c17e24d88d merge revision(s) 88bfa3005c:
[PATCH] Fix buffer overflow in String#rpartition
	MIME-Version: 1.0
	Content-Type: text/plain; charset=UTF-8
	Content-Transfer-Encoding: 8bit

	The conversion of the pattern to string can cause the string to be modified.
	This can cause a buffer overflow since it uses the original length of the
	string. For example, the following script reports a buffer overflow with
	ASAN:

	    s = "héllo" * 1000
	    obj = Object.new
	    obj.define_singleton_method(:to_str) do
	      s.replace("hé")
	      "l"
	    end
	    p s.rpartition(obj)
2026-09-22 18:39:10 +09:00
nagachika 044f08d26d merge revision(s) edce07a8c9:
[PATCH] Fix use-after-free in String#% when array modified

	If the array object passed into String#% is modified during conversion
	(such as via to_s, to_i, to_f, etc.) then it can be a use-after-free if
	the buffer of the array is freed.

	This commit changes it to detect if the buffer has been changed and raises
	an error when that happens.

	The following script reproduces the crash:

	    count = 1_000
	    obj = Object.new
	    ary = [obj, *Array.new(count - 1) { "x" }]
	    obj.define_singleton_method(:to_s) do
	      ary.replace([])
	      "X"
	    end
	    str = "%s" * count
	    puts (str % ary).length
2026-09-21 17:18:30 +09:00
nagachika d8ada47f21 merge revision(s) d4d0786729:
[PATCH] Fix out-of-bounds access in String#byteindex/byterindex
	MIME-Version: 1.0
	Content-Type: text/plain; charset=UTF-8
	Content-Transfer-Encoding: 8bit

	If the source string is modified when converting offset to an integer,
	then there could be an out-of-bounds access because the length of the
	string is captured before to_int is called. For example, the following
	script triggers an ASAN error:

	    s = "héllo" * 1_000_000
	    obj = Object.new
	    obj.define_singleton_method(:to_int) do
	      s.replace("é" * 1000)
	      450000
	    end
	    p s.byteindex("l", obj)
2026-09-21 17:16:16 +09:00
nagachika bce3c89801 merge revision(s) b61da996af: [Backport #22336]
[PATCH] [Bug #22336] Fix use-after-free in `IO::Buffer#get_string`. (#18919)
2026-09-20 15:26:43 +09:00
nagachika 350ef31253 merge revision(s) d7fe390c85: [Backport #22334]
[PATCH] Fix crash when returning splat

	[Bug #22334]

	If we return a with a keyword splat, then each element does not necessarily
	push two values onto the stack, so we cannot compile this as a simple sequence
	of key-value pairs combined with newhash. The following script reproduces
	the issue and crashes:

	    def r
	      return **{foo: 1}
	    end
	    puts r()
2026-09-20 15:26:43 +09:00
nagachika 9bfc30e701 merge revision(s) d38952c45e: [Backport #22321]
[PATCH] Fix Method#inspect when klass is iclass

	[Bug #22321]

	In the following script, the klass of the Method object is an iclass, which
	will raise an error "NoMethodError: undefined method 'inspect' for an instance of M":

	    module M
	      def foo = 1
	    end

	    class C
	      include M
	    end

	    obj = C.new
	    meth = obj.method(:foo).unbind.bind(obj)
	    puts meth.inspect
2026-09-20 15:26:43 +09:00
nagachika b82e505890 merge revision(s) 20ca4e1f64: [Backport #22322]
[PATCH] [Bug #22322] Take transcoder names from declared transcoders

	Fix use-after-free of dangling pointers.
2026-09-20 15:26:43 +09:00
nagachika 2a77c0f6f9 merge revision(s) 0c9f3f1e68: [Backport #22319]
[PATCH] Fix use-after-free when clearing array during zip

	[Bug #22319]

	Array#zip triggers an use-after-free it does not account for when the call
	to rb_check_array_type modifies the source array. The following script crashes:

	    a = (1..100_000).to_a
	    evil = Object.new; $a = a
	    def evil.to_ary; $a.clear; [1,2,3]; end
	    a.zip(evil)
2026-09-20 15:26:43 +09:00
nagachika 3ebd235fb6 merge revision(s) 5eeaedc3d3: [Backport #22318]
[PATCH] Fix use-after-free when clearing array during flatten

	[Bug #22318]

	Array#flatten triggers an use-after-free it does not account for when the
	call to rb_check_array_type modifies the source array. The following script
	crashes:

	    a = (1..100_000).to_a
	    $a = a
	    bad = Object.new
	    def bad.to_ary; $a.clear; [1,2,3]; end
	    a << bad
	    a.flatten
2026-09-20 15:26:43 +09:00
nagachika ce5cca10a5 merge revision(s) 685144e3d6: [Backport #22315]
[PATCH] Fix crash when source string modified in String#unpack

	[Bug #22315]

	The following script crashes because there is a use-after-free on the
	source string during String#unpack:

	    s = "C" * 4000000
	    s.unpack("L*") { s.clear }

	String#unpack will now instead raise RuntimeError with message "string modified".
2026-09-20 15:26:43 +09:00
nagachika cf53d1a728 merge revision(s) 5831948a66: [Backport #22303]
[PATCH] Fix the `intern` VM instruction to be `leaf=false` (also in YJIT, ZJIT)

	rb_str_intern can raise EncodingError and call its initializer.
	This call can push a Ruby frame.

	Fix in the VM, YJIT, and ZJIT.
2026-09-20 14:26:37 +09:00
nagachika 72aa33a126 merge revision(s) e5132b1627: [Backport #22264]
[PATCH] [Bug #22264] Warn for duplicate keys after leading keyword splat

	Leading keyword splats must not suppress duplicate literal key
	warnings.
2026-09-20 14:09:25 +09:00
nagachika 1e1e90990c merge revision(s) 46c287e2ca: [Backport #22259]
[PATCH] Fix splicing an array that shares its buffer with a longer array

	Arrays sharing one buffer have the same data pointer, so rb_ary_splice()
	took `a` in `b = a.dup; b.pop; b.concat(a)` for `b` itself and rebased
	the source onto `b`'s shorter storage, copying uninitialized memory.
	Have the callers tell whether the replacement really is `ary`.

	[Bug #22259]

	Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 14:09:02 +09:00
Matt Valentine-House c8e05b5885 [Backport #22257] Fix stale super cache on prepend after include
Prepending a module to a module that already has includers backfills an
origin iclass into each includer's ancestor chain using rb_prepend_module.

The backfilled iclass is never added to a subclasses list, so
rb_clear_method_cache can't reach it when one of the module's methods is
later redefined.

Calling super through a call site that has cached the backfilled iclass
calls the old entry, even though it should be redefined.

```ruby
module M; def foo; :m; end; end
class D; include M; end
M.prepend(Module.new { def foo; super; end })
D.new.foo                          # prime the super cache
M.send(:define_method, :foo) { :hooked }
D.new.foo                          # => got :m, expected :hooked
```

This commit makes sure that the backfilled iclass is registered in the
subclasses list.
2026-09-06 17:38:25 +09:00
Jean Boussier 56089b4f0a Do not increase ::Object and ::BasicObject max_iv_count
[Bug #22290]

Otherwise any code defining instance variables on `::Object`
(including `main`) will bloat all classes defined afterwards.
2026-09-06 15:00:16 +09:00
nagachika 7096aad15f merge revision(s) 79e6d37b55: [Backport #22218]
[PATCH] Preserve line events across jump optimization. (#18122)

	[Bug #22218]
2026-08-09 16:03:39 +09:00
nagachika d784a0b8a5 merge revision(s) 239a472609, d29afc8ab8: [Backport #20958]
[PATCH] fix ENV.keys encoding on windows

	Fix the encoding of ENV.keys elements to UTF-8 on windows.
	They contain UTF-8 byte sequences with locale encoding.

	[PATCH] [Bug #20958] Fix test_keys_encoding on hosts without en_US.UTF-8

	`EnvUtil.invoke_ruby` forces the C locale on the child process, so the
	test set `LC_ALL=en_US.UTF-8` to get a UTF-8 locale back.  That locale
	is not generated on the debian11 chkbuild host, where `setlocale` falls
	back to C and the ENV key comes out as ASCII-8BIT instead of the
	default internal encoding.  Inherit the locale of the parent process
	instead, and omit the test when that locale is not UTF-8.

	https://rubyci.s3.amazonaws.com/debian11/ruby-master/log/20260725T093003Z.fail.html.gz
2026-08-09 15:59:47 +09:00
nagachika e1bb38b7a6 merge revision(s) 0d7e50a174: [Backport #22191]
[PATCH] [Bug #22191] Report `EXIT` handler exceptions after `END` blocks

	Keep exceptions raised by `EXIT` traps until `END` handlers finish.
	That leaves them available for reporting during cleanup.

	Print `END` handler exceptions without replaying causes already
	reported from earlier exit handlers. This keeps the previous handler
	exception visible while avoiding duplicate diagnostics.
2026-08-09 15:52:00 +09:00
nagachika 7b0b33ab9b merge revision(s) aaf54aa08b: [Backport #22189]
[PATCH] [Bug #22189] Lazy `to_enum` should accept `String` method names too

	Since `lazy_use_super_method` is the `Symbol`-to-`Symbol` mapping,
	convert the given method name to a `Symbol` before consulting that
	table.
2026-08-09 15:51:35 +09:00
nagachika d6dbd1dc4c merge revision(s) ef67c37750: [Backport #22133]
[PATCH] Respect `handle_interrupt` for default `SIGINT`. (#17533)

	[[Bug #22133]]
2026-08-09 15:51:00 +09:00
nagachika 6162535e5d merge revision(s) 034fa35ff0: [Backport #22099]
[PATCH] CI shape comparison needs to take into account # of kwargs given

	If number of keyword arguments are not taken into account when comparing CI
	shapes, invalid bindings happen:

	```
	class Foo
	  def bar(a: nil, b: nil)
	    p [:body, a: a, b: b]
	  end
	end

	foo = Foo.new

	foo.bar(a: 1, b: 2)   #=> [:body, {a: 1, b: 2}] # prime CC
	foo.bar(99, a: 0)     #=> [:body, {a: 99, b: nil}] # BAD!
	```

	Also, set max keywords to 2**16-1 so we don't grow the cc_entries_entry struct.

	Fixes [Bug #22099]
2026-08-09 15:06:30 +09:00
nagachika ee91328658 merge revision(s) 3f7d4bc859: [Backport #22183]
[PATCH] [Bug #22183] Preserve `lpar_beg` across interpolation

	Fix unexpected `keyword_do_LAMBDA` inside string interpolation within
	default argument of lambda.
2026-08-09 14:39:23 +09:00
nagachika f065974c22 merge revision(s) d8444812f4: [Backport #22124]
[PATCH] [Bug #22124] Add block to the proper node
2026-08-09 14:39:00 +09:00
nagachika 8a159ffbd6 merge revision(s) 1f473ed633: [Backport #22126]
[PATCH] [Bug #22126] Fix partial DCE with loop back-edges

	The old `remove_unreachable_chunk` walked the dead code once and tried
	to do two things at the same time -- count label references inside that
	chunk and figure out what exactly to delete.

	That breaks for loops -- the loop label comes before the jump back to it;
	on the first walk, the label looks like it has no references inside the
	chunk yet, so the optimizer thinks something outside the dead code still
	needs it. It stops there and deletes only the setup code before the loop,
	leaving the loop body behind.

	I've changed the algorithm to use two passes to mitigate the issue;
	I believe the performance overhead is neglible

	```shell
	$ ruby --parser parse.y -e 'if []; else; a => [*, 42, *]; end'
	$ ruby --parser prism -e   'if []; else; a => [*, 42, *]; end'
	-- raw disasm--------
	   trace: 1
	   0000 putnil                                                           (   1)
	   0001 leave                                                            (   1)
	 <L018> [sp: 1, unremovable: 0, refcnt: 1]
	   0002 dup                                                              (   1)
	   0003 topn                 2                                           (   1)
	   0005 opt_le               <calldata:<=, 1>                            (   1)
	   0007 branchunless         <L021>                                      (   1)
	   0009 topn                 3                                           (   1)
	   0011 topn                 1                                           (   1)
	   0013 opt_aref             <calldata:[], 1>                            (   1)
	   0015 putobject            42                                          (   1)
	   0017 dupn                 2                                           (   1)
	   0019 checkmatch           2                                           (   1)
	   0021 dup                                                              (   1)
	   0022 branchif             <L023>                                      (   1)
	   0024 putspecialobject     1                                           (   1)
	   0026 putobject            "%p === %p does not return true"            (   1)
	   0028 topn                 3                                           (   1)
	   0030 topn                 5                                           (   1)
	   0032 opt_send_without_block <calldata:core#sprintf, 3>                (   1)
	   0034 setn                 10                                          (   1)
	   0036 putobject            false                                       (   1)
	   0038 setn                 12                                          (   1)
	   0040 pop                                                              (   1)
	   0041 pop                                                              (   1)
	 <L023> [sp: 4, unremovable: 0, refcnt: 1]
	   0042 setn                 2                                           (   1)
	   0044 pop                                                              (   1)
	   0045 pop                                                              (   1)
	   0046 branchif             <L020>                                      (   1)
	   0048 putobject_INT2FIX_1_                                             (   1)
	   0049 opt_plus             <calldata:+, 1>                             (   1)
	   0051 jump                 <L018>                                      (   1)
	 <L021> [sp: 1, unremovable: 0, refcnt: 1]
	*  0053 adjuststack          3                                           (   1)
	   0055 putspecialobject     1                                           (   1)
	   0057 putobject            "%p does not match to find pattern"         (   1)
	   0059 topn                 2                                           (   1)
	   0061 opt_send_without_block <calldata:core#sprintf, 2>                (   1)
	   0063 setn                 4                                           (   1)
	   0065 putobject            false                                       (   1)
	   0067 setn                 6                                           (   1)
	   0069 pop                                                              (   1)
	   0070 pop                                                              (   1)
	   0071 jump                 <L012>                                      (   1)
	 <L020> [sp: 1, unremovable: 0, refcnt: 1]
	   0073 adjuststack          3                                           (   1)
	   0075 pop                                                              (   1)
	   0076 jump                 <L003>                                      (   1)
	 <L012> [sp: -1, unremovable: 0, refcnt: 1]
	   0078 pop                                                              (   1)
	   0079 putspecialobject     1                                           (   1)
	   0081 topn                 4                                           (   1)
	   0083 branchif             <L024>                                      (   1)
	   0085 putobject            NoMatchingPatternError                      (   1)
	   0087 putspecialobject     1                                           (   1)
	   0089 putobject            "%p: %s"                                    (   1)
	   0091 topn                 4                                           (   1)
	   0093 topn                 7                                           (   1)
	   0095 opt_send_without_block <calldata:core#sprintf, 3>                (   1)
	   0097 opt_send_without_block <calldata:core#raise, 2>                  (   1)
	   0099 jump                 <L025>                                      (   1)
	 <L024> [sp: -1, unremovable: 0, refcnt: 1]
	   0101 putobject            NoMatchingPatternKeyError                   (   1)
	   0103 putspecialobject     1                                           (   1)
	   0105 putobject            "%p: %s"                                    (   1)
	   0107 topn                 4                                           (   1)
	   0109 topn                 7                                           (   1)
	   0111 opt_send_without_block <calldata:core#sprintf, 3>                (   1)
	   0113 topn                 7                                           (   1)
	   0115 topn                 9                                           (   1)
	   0117 opt_send_without_block <calldata:new, 3>                         (   1)
	   0119 opt_send_without_block <calldata:core#raise, 1>                  (   1)
	 <L025> [sp: -1, unremovable: 0, refcnt: 1]
	   0121 adjuststack          7                                           (   1)
	   0123 putnil                                                           (   1)
	   0124 leave                                                            (   1)
	 <L003> [sp: -1, unremovable: 0, refcnt: 1]
	   0125 adjuststack          6                                           (   1)
	   0127 putnil                                                           (   1)
	   0128 leave                                                            (   1)
	---------------------
	-e:1: argument stack underflow (-2)
	-e: compile error (SyntaxError)
	```

	Reproducible on every version since `3.4.0-preview2`
	(`docker run -e ALL_RUBY_SHOW_DUP=yes -e ALL_RUBY_SINCE=3.4 --rm rubylang/all-ruby ./all-ruby -We 'if []; else; a => [*, 42, *]; end'`)

	There seem to be another issue related to Prism's DCE & loops:

	```shell
	$ ruby --parser parse.y --dump insn -e 'if []; else; while true; end; end'
	== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,33)>
	0000 putnil                                                           (   1)[Li]
	0001 leave
	$ ruby --parser prism --dump insn -e 'if []; else; while true; end; end'
	== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,33)>
	0000 newarray                               0                         (   1)[Li]
	0002 branchunless                           11
	0004 putnil
	0005 leave
	0006 pop
	0007 jump                                   11
	0009 putnil
	0010 pop
	0011 jump                                   11
	0013 putnil
	0014 leave
	```

	the reason `branchunless` is not eliminated is that prism does not emit
	`NODE_ZLIST` like `parse.y` does and instead emits `PM_ARRAY_NODE-0`
	which is not treated as a always-truthy value by the peephole-optmizer.
2026-08-09 14:32:59 +09:00
Samuel Williams b92c8c2253 Guard fiber target across fiber_store. (#17951)
Fixes [Bug #22196].
2026-07-18 13:08:53 +09:00
Samuel Williams 44d5a6fce4 Revert "Guard fiber target across fiber_store. (#17951)"
This reverts commit e377cd5e8e.
2026-07-17 21:18:57 +12:00
Samuel Williams e377cd5e8e Guard fiber target across fiber_store. (#17951)
Fixes [Bug #22196].
2026-07-17 16:17:32 +12:00
Benoit Daloze e9ace5404c Fix ar_find_entry_hint() to handle #eql? or another thread changing bound or converting to st_table
* While perusing code in hash.c I found it suspicious that ar_find_entry_hint()
  didn't reread bound in the loop and yet called arbitrary code through #eql?.
* ar_find_entry_hint() before this commit would not check if bound or the storage (AR->ST) changed
  and would return a bin index which is not correct to access.
* Check bound in the loop so it's always up-to-date.
* Return RHASH_AR_TABLE_CONVERTED_TO_ST_TABLE when converted to st_table
  and make callers retry the operation as a st_table.

[Bug #22120]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
(cherry picked from commit c88430e187)
2026-06-29 19:15:38 +09:00
Nobuyoshi Nakada f8d0dcdfec [Bug #22092] Improve Array#sum when the initial value is a Float 2026-06-29 19:04:46 +09:00
nagachika e0eec88ee5 merge revision(s) 2552db04dd, 95626e3a9c, 90ed85f91e, 3373fcc2de, 4bd3e14fc2, 2c8002d583, 773e0c3a0f: [Backport #22112]
[PATCH] Fix UAF in IO::Buffer#& when self or mask is an invalidated slice

	[PATCH] Fix UAF in IO::Buffer#| when self or mask is an invalidated slice

	`io_buffer_or` accessed `buffer->base` and `mask_buffer->base` directly without validating that the buffers were still live. A slice whose parent had been freed retained its stale base pointer, so calling `|` on it caused a UAF.

	Use `io_buffer_get_bytes_for_reading` for both operands, which raises `IO::Buffer::InvalidatedError` before any memory access if either buffer has been invalidated.

	[PATCH] Fix UAF in IO::Buffer#^ when self or mask is an invalidated slice

	`io_buffer_xor` accessed `buffer->base` and `mask_buffer->base` directly without validating that the buffers were still live. A slice whose parent had been freed retained its stale base pointer, so calling `^` on it caused a UAF.

	Use `io_buffer_get_bytes_for_reading` for both operands, which raises `IO::Buffer::InvalidatedError` before any memory access if either buffer has been invalidated.

	[PATCH] Fix UAF in IO::Buffer#~ when self is an invalidated slice

	`io_buffer_not` accessed `buffer->base` directly without validating that the buffer was still live. A slice whose parent had been freed retained its stale base pointer, so calling `~` on it caused a UAF.

	Use `io_buffer_get_bytes_for_reading`, which raises `IO::Buffer::InvalidatedError` before any memory access if the buffer has been invalidated.

	[PATCH] IO::Buffer: Validate the mask argument of bit operations

	[PATCH] IO::Buffer: Validate the buffer after argument conversion

	[PATCH] IO::Buffer: Validate the buffer after type argument conversion
2026-06-14 18:24:21 +09:00