On x86_64-linux hosts `--cooldown 0` resolves to the platform-specific
build, so `include_gems` failed expecting the ruby platform.
https://github.com/ruby/rubygems/commit/d7be25160e
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Exclusion is keyed on [name, version] and deliberately ignores
platform, so pushing a fresh platform-specific build under an
already-ripe version number cannot slip new code past the cooldown.
No spec covered that behavior.
https://github.com/ruby/rubygems/commit/be334bd252
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both commands resolve, but only install, update, add and outdated
accepted the flag, so the resolver's failure hint suggesting
`--cooldown 0` was not actionable on `bundle lock --update`.
https://github.com/ruby/rubygems/commit/2c4f4f390a
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
gen_invokeblock_ifunc drops the callinfo and passes argv straight to
rb_vm_yield_with_cfunc, which hardcodes kw_splat = 0, so keyword
arguments lost their keyword-ness. Gate the ifunc specialization on
block_call_inlinable, the same check used for the inline ISEQ path.
The GC sweep fast path had an inverted condition for hashes in
rb_gc_obj_needs_cleanup_p. Hashes with an st_table needs to be freed.
The following script leaks memory:
10.times do
100_000.times do
# Hashes with more than 8 elements use st_table with a malloc'd
# entries array
h = {}
9.times { |i| h[i] = i }
end
GC.start
puts `ps -o rss= -p #{$$}`
end
Before:
58816
97056
135296
173584
211840
250096
288336
326608
364848
403088
After:
27696
27696
27696
27696
27696
27696
27696
27696
27696
27696
OpenSSL::PKCS7#decrypt is unreliable when given only a private key and
the PKCS#7 structure contains multiple recipients. It may occasionally
raise an exception or return garbage data. Update the test to avoid
this.
This became much more visible in CI after upgrading to OpenSSL versions
released on 2026-06-09, which enable the implicit rejection mechanism
for PKCS#1 v1.5 decryption in PKCS#7.
https://github.com/ruby/openssl/commit/f7c51a2c0c
The `once` insn (ex: for interpolated regexp with /o option) should only be
executed once even across Ractors. The `once` value is associated with
the iseq, which is shared across Ractors.
Ideally, waiting threads in other Ractors would be put in a queue and
signalled when the winning thread in another Ractor wins the race and
executes the iseq successfully. For now, there's potential for busy
looping when Ractors wait on each other. However, the bug is fixed and
the iseq will only be executed once.
* ZJIT: Fix trivial inlining for optional argument entries
Use SendDirect's jit_entry_idx when reading the callee bytecode for
trivial inlining, so optional-argument calls inspect the same entry
point that SendDirect selected.
This avoids inlining default-argument initialization code when the
caller actually provided the optional positional argument.
* ZJIT: Require trivial inline target to end at leave
Limit the trivial inliner to whole-ISEQ shapes that start at bytecode
index 0 and end with the second instruction's leave. Optional argument
entries can start in the middle of the ISEQ, so keep those SendDirect
calls for the general inliner, which handles jit_entry_idx.
This avoids folding default-argument initialization code when the caller
actually supplied the optional positional argument.
Co-authored-by: Takashi Kokubun (k0kubun) <takashikkbn@gmail.com>
Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
Ractor::Port has a public allocator, so Ractor::Port.allocate returns a port whose owner Ractor is still NULL; only ractor_port_init() fills it in. The methods dereferenced it unchecked:
Ractor::Port.allocate.closed? # [BUG] Segmentation fault at 0x60
Route the paths reachable from Ruby through ractor_port_ptr_check(), which raises TypeError as other uninitialized typed data objects do. The self of #initialize_copy keeps the unchecked accessor because it is legitimately uninitialized there; only orig is checked.
Ractor.select did not crash, but such a port has id 0, the same as the current Ractor's default port, so it took messages from it.
Port#closed? also loses Primitive.attr! :leaf. It can raise now, and it was not a leaf before either, since the foreign Ractor path takes RACTOR_LOCK().
[Bug #22214]
I benchmarked various values of STR_DUPLICATE_MAX_EMBED_LEN and the
performance impacts. For String#dup, creating a view is faster until it
is below 256 bytes. Unsurprisingly, if we add modification to the string,
then it is faster to eagerly create a copy than use a view. Surprisingly,
if we append to the string, it's still faster to go from embedded -> extended
than shared -> extended. However, both of these cases will incur a memory
penalty as the remainder of the slot is wasted memory.
Benchmark:
require "benchmark"
TIMES = 10_000_000
Benchmark.bm do |x|
[64, 128, 256, 512, 1024].each do |len|
len -= 25 # Subtract header length
x.report("String#dup for #{len.to_s}") do
pid = fork do
str = ("a" * len).freeze
i = 0
while i < TIMES
str.dup
i += 1
end
end
Process.wait(pid)
end
x.report("String#dup + String#[]= for #{len.to_s}") do
pid = fork do
str = ("a" * len).freeze
i = 0
while i < TIMES
dup = str.dup
dup[0] = "1"
i += 1
end
end
Process.wait(pid)
end
x.report("String#dup + String#<< for #{len.to_s}") do
pid = fork do
str = ("a" * len).freeze
i = 0
while i < TIMES
dup = str.dup
dup << "1"
i += 1
end
end
Process.wait(pid)
end
end
end
Result:
1024
user system total real
String#dup for 39 0.000085 0.000646 0.207714 ( 0.208041)
String#dup + String#[]= for 39 0.000067 0.000379 0.499957 ( 0.500514)
String#dup + String#<< for 39 0.000069 0.000413 0.600205 ( 0.600984)
String#dup for 103 0.000068 0.000374 0.236938 ( 0.237424)
String#dup + String#[]= for 103 0.000063 0.000401 0.501791 ( 0.502506)
String#dup + String#<< for 103 0.000066 0.000395 0.600041 ( 0.600870)
String#dup for 231 0.000066 0.000370 0.281924 ( 0.282409)
String#dup + String#[]= for 231 0.000064 0.000355 0.574779 ( 0.575599)
String#dup + String#<< for 231 0.000070 0.000395 0.691011 ( 0.691972)
String#dup for 487 0.000070 0.000366 0.355631 ( 0.356230)
String#dup + String#[]= for 487 0.000067 0.000391 0.653774 ( 0.654509)
String#dup + String#<< for 487 0.000066 0.000359 0.891413 ( 0.892607)
String#dup for 999 0.000068 0.000394 0.527350 ( 0.528033)
String#dup + String#[]= for 999 0.000067 0.000378 0.786794 ( 0.787682)
String#dup + String#<< for 999 0.000063 0.000363 1.070604 ( 1.071885)
512
user system total real
String#dup for 39 0.000083 0.000639 0.207305 ( 0.207687)
String#dup + String#[]= for 39 0.000067 0.000398 0.501968 ( 0.502645)
String#dup + String#<< for 39 0.000071 0.000414 0.586533 ( 0.587365)
String#dup for 103 0.000064 0.000397 0.222104 ( 0.222600)
String#dup + String#[]= for 103 0.000069 0.000389 0.483612 ( 0.484240)
String#dup + String#<< for 103 0.000069 0.000380 0.606659 ( 0.607510)
String#dup for 231 0.000067 0.000388 0.292563 ( 0.293070)
String#dup + String#[]= for 231 0.000064 0.000389 0.573774 ( 0.574479)
String#dup + String#<< for 231 0.000069 0.000413 0.739569 ( 0.740614)
String#dup for 487 0.000067 0.000419 0.370516 ( 0.371072)
String#dup + String#[]= for 487 0.000060 0.000337 0.676507 ( 0.677282)
String#dup + String#<< for 487 0.000074 0.000410 0.911670 ( 0.916480)
String#dup for 999 0.000069 0.000397 0.239633 ( 0.240049)
String#dup + String#[]= for 999 0.000066 0.000370 1.196397 ( 1.198272)
String#dup + String#<< for 999 0.000072 0.000396 1.683562 ( 1.686244)
256
user system total real
String#dup for 39 0.000073 0.000588 0.198103 ( 0.198485)
String#dup + String#[]= for 39 0.000067 0.000397 0.475345 ( 0.476011)
String#dup + String#<< for 39 0.000074 0.000408 0.589001 ( 0.589778)
String#dup for 103 0.000073 0.000420 0.244381 ( 0.244818)
String#dup + String#[]= for 103 0.000065 0.000421 0.480720 ( 0.481569)
String#dup + String#<< for 103 0.000070 0.000414 0.589164 ( 0.590123)
String#dup for 231 0.000077 0.000447 0.272169 ( 0.272640)
String#dup + String#[]= for 231 0.000067 0.000425 0.517280 ( 0.518037)
String#dup + String#<< for 231 0.000064 0.000409 0.681593 ( 0.682572)
String#dup for 487 0.000065 0.000390 0.229708 ( 0.230153)
String#dup + String#[]= for 487 0.000067 0.000395 0.872966 ( 0.874233)
String#dup + String#<< for 487 0.000071 0.000411 1.317758 ( 1.320085)
String#dup for 999 0.000070 0.000397 0.255197 ( 0.255609)
String#dup + String#[]= for 999 0.000068 0.000380 1.174781 ( 1.176700)
String#dup + String#<< for 999 0.000067 0.000389 1.729608 ( 1.738797)
128
user system total real
String#dup for 39 0.000105 0.000950 0.205384 ( 0.206565)
String#dup + String#[]= for 39 0.000066 0.000406 0.483924 ( 0.485308)
String#dup + String#<< for 39 0.000075 0.000433 0.590150 ( 0.591641)
String#dup for 103 0.000076 0.000414 0.225900 ( 0.227975)
String#dup + String#[]= for 103 0.000098 0.000604 0.499234 ( 0.503926)
String#dup + String#<< for 103 0.000070 0.000400 0.619562 ( 0.627411)
String#dup for 231 0.000073 0.000445 0.241433 ( 0.243606)
String#dup + String#[]= for 231 0.000067 0.000402 0.843665 ( 0.853293)
String#dup + String#<< for 231 0.000071 0.000392 0.990903 ( 0.994555)
String#dup for 487 0.000073 0.000403 0.234920 ( 0.236132)
String#dup + String#[]= for 487 0.000077 0.000445 0.944582 ( 0.956001)
String#dup + String#<< for 487 0.000069 0.000410 1.337099 ( 1.341250)
String#dup for 999 0.000072 0.000420 0.233520 ( 0.234182)
String#dup + String#[]= for 999 0.000072 0.000419 1.186288 ( 1.188972)
String#dup + String#<< for 999 0.000069 0.000436 1.685112 ( 1.689536)
The call-seq of IO::Buffer#<=> says it returns "true or false", but
rb_io_buffer_compare always returns an integer: -1 or 1 when the sizes
differ, and the result of memcmp as is when the sizes are equal. Since
the value comes straight from memcmp it is not normalized to -1, 0 or 1,
so only its sign is meaningful.
p(IO::Buffer.for("abc") <=> IO::Buffer.for("abc")) # => 0
p(IO::Buffer.for("abc") <=> IO::Buffer.for("ab")) # => 1
p(IO::Buffer.for("abc") <=> IO::Buffer.for("abz")) # => -23
IO::Buffer#hexdump returns nil when the buffer does not reference any
memory, that is, when #null? returns true, but this was not documented.
buffer = IO::Buffer.new(4)
buffer.free
p buffer.hexdump # => nil
The example shows the result as `+4`, but the operation returns a buffer of
the same size as the source, which is 10 bytes here:
$ ruby -e 'p((IO::Buffer.for("1234567890") & IO::Buffer.for("\xFF\x00\x00\xFF")).size)'
10
The rest of the example is already correct. The hex dump and the ASCII column
show all 10 bytes, and the description right above says "Generate a new buffer
the same size as the source", so the example contradicts both. The size of the
mask is 4, which is likely where the number came from.
The neighbouring examples for #|, #^ and #~ all show `+10` and need no change.
There's some special handling in regards to empty symbols and those containing newlines
that should apply here. Makes it produce the correct amount of sym/dsym nodes
https://github.com/ruby/prism/commit/8b567cc403
parse_expression_terminator runs after every infix operator and called
pm_block_call_p, which walks the whole receiver chain, even when the
binding power alone already decided the result. Check the binding power
first; pm_block_call_p is pure, so the condition is unchanged.
Parsing "a" followed by 8000 ".b" links drops from 44.8ms to 0.46ms.
https://github.com/ruby/prism/commit/8a40a928a8
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The :path option was misread as a way to install a packaged .gem
file. State explicitly that it expects a directory with the gem's
unpacked source code.
https://github.com/ruby/rubygems/commit/dc7fdaa2a6
jmp_ptr_bytes() reserves room for a patchable jump -- it sets
page_end_reserve and bounds conditional jump width. A single `b` suffices
when every branch in the code region is in imm26 range; otherwise it
reserves 5 instructions for an absolute load-address plus br.
The range test was `virtual_region_size() / 4`, but two instructions in an
S-byte region are at most S-4 bytes apart. At exactly 128MiB, S/4 is 2^25
while the widest imm26 offset is 2^25-1, so the check gave up one
instruction early and took the 5-instruction fallback -- despite the comment
right above it saying <= 128 should work.
That boundary is the default. exec_mem_size falls back to mem_size in
CodegenGlobals::init and mem_size defaults to 128MiB, so every arm64 run
without an explicit --yjit-exec-mem-size reserved 20 bytes per patchable
jump where 4 would do. On a branch-heavy method that is the difference
between 1336 and 956 bytes of inline code.
Test (S-4)/4 instead. The decision moves into a small jmp_ptr_bytes_for_region()
so a unit test can pin both sides of the boundary without allocating a region
that large. --yjit-exec-mem-size=129 still takes the fallback, as before.
Checked with `make yjit-check` on arm64 macOS and on Linux/Graviton 4: 374
cargo tests, all bootstraptest at --yjit-call-threshold=1, and test_yjit.rb
(142 tests, 0 failures). Since the default size is the boundary case, the
whole suite ran on the tightened reservation.
This changes the following methods to compact the set after removal:
* `^`
* `-`
* `difference`
* `subtract`
* `keep_if`
* `select!`
* `filter!`
Fixes [#22210]
The documentation for `IO::Buffer#set_value` shows:
buffer.set_value(:U8, 1, 111)
# => 1
but it actually returns 2, the offset just after the written value:
$ ruby -e 'p IO::Buffer.new(8).set_value(:U8, 1, 111)'
2
`io_buffer_set_value` passes `&offset` to `rb_io_buffer_set_value`, which
advances it by the size of the written value, and then returns the advanced
offset:
static VALUE
io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value)
{
struct rb_io_buffer *buffer = get_io_buffer_for_writing(self);
size_t offset = io_buffer_extract_offset(_offset);
rb_io_buffer_set_value(buffer, type, &offset, value);
return SIZET2NUM(offset);
}
This changed in 025b8701c09813c40339bd8fa1c75e0e5eaf7120 (#6434, released in
3.2.0); before that the method returned the given offset, so the example was
correct at the time it was written. Checked with all-ruby: 3.1 returns 1 and
3.2 through 4.0 return 2.
Also state the return value explicitly for both `set_value` and `set_values`.
The call-seq says `-> offset`, which reads as if the given offset were
returned. `set_values` returns the offset after the last written value in the
same way (3 for the example in its documentation).
bundle-env, bundle-fund, bundle-info, bundle-issue, bundle-licenses,
bundle-list, and bundle-pristine all have their own man pages but were
not listed in bundle(1), so they were never linked from the command
index.
https://github.com/ruby/rubygems/commit/9e37b2fd9f
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The handwritten Gemfile guide on guides.rubygems.org is planned to be
replaced by this generated man page, but its explanation of the ~>
specifier and the eval_gemfile method have no equivalent here. Port
both ahead of the replacement so the information is not lost.
https://github.com/ruby/rubygems/commit/2166ff6665
[Bug #11438]
VirtualQuery against a local variable address may return a region that
does not span the whole thread stack when the interpreter is
initialized deep in the stack, such as Ruby embedded in another
application. stack_check() then misfires with a bogus SystemStackError.
String#unspace escapes whitespace with a backslash, which is a GNU
make convention that nmake does not interpret, and nmake cannot
express a directory containing spaces in `{dir}` inference paths in
any form. Building an extension from such a path silently generates
object rules that never fire. Warn at Makefile creation time
instead of failing later with a confusing link error.
Apply independent timeout factors for macOS runners and the MMTk
collector. This extends Ubuntu MMTk subprocess timeouts without
retaining an excessive scale for the default collector on macOS.