100615 Commits

Author SHA1 Message Date
Hiroshi SHIBATA
acc86e6d5c [ruby/rubygems] Warn when duplicate source declarations conflict on cooldown
Declaring the same source URL twice with different cooldown values
silently drops one of them, because duplicate declarations are deduped
into a single source and cooldown applies per server, not per gem.
The reporter of #9723 used a second source block with cooldown: 0 as a
per-gem exemption and got no indication it had no effect. Keep the
dedup behavior but warn when a conflicting cooldown value is discarded.

https://github.com/rubygems/rubygems/issues/9723

https://github.com/ruby/rubygems/commit/a2a44cf4e0

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 06:36:11 +00:00
Shizuo Fujita
553f19d9b2
[DOC] Fix the $? description of Process::Status.wait (#18109)
The documentation says that Process::Status.wait sets the thread-local
variable $? when there are child processes, but it never does.
rb_process_status_wait does not call rb_last_status_set, and $? is left
untouched:

    $?                        # => nil
    Process.spawn('cat /nop') # => 4996
    Process::Status.wait      # => #<Process::Status: pid 4996 exit 1>
    $?                        # => nil

The existing example showed this as well: the pid displayed for $?
(1155508) did not match the pid of the returned status (1155880),
contradicting the surrounding text.

Since $? is never set, the "does not set thread-local variable $?"
note in the no-child-process case is now redundant and has been folded
into the general description.
2026-07-30 15:24:42 +09:00
Hiroshi SHIBATA
e15f28c57d [ruby/rubygems] Redact URI userinfo from settings names in Bundler User-Agent
The `options/` segment of Bundler's User-Agent is built from
`Bundler.settings.all`, which returns only setting names. Mirror
settings, however, embed a URI in the key itself
(`mirror.http://user:token@host/`), so the URI userinfo becomes part of
the name. That name is then sent to every gem source Bundler contacts,
including public or attacker-controlled sources, leaking the embedded
credentials cross-origin.

Strip the userinfo from any settings key that embeds a URI when building
the User-Agent, keeping the useful telemetry (which settings are in use
and the host) while dropping the secret. The redaction is limited to the
User-Agent; `Bundler.settings.all` still returns the real keys, which
`local_overrides` and `gem_mirrors` rely on.

https://github.com/ruby/rubygems/commit/1468529cbe

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 05:42:26 +00:00
Hiroshi SHIBATA
145a4bba92 [ruby/rubygems] Validate gem name before building compact index cache paths
Bundler::CompactIndexClient::Cache#info_path and #info_etag_path join
the gem name into the cache directory without validation. The name comes
from the remote index, either versions lines or transitive dependency
names in info files, so a crafted name like "../../../../pwn" escapes
the cache directory because the special-characters branch keeps the raw
name and only appends an MD5 suffix. Reject any name that is not a plain
basename before constructing the path, matching the fetch_spec guard
from https://github.com/ruby/rubygems/commit/56ed326cb2, and apply the same guard to the independent
Gem::CompactIndexClient::Cache port.

https://github.com/ruby/rubygems/commit/4b1c454526

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 05:42:06 +00:00
Max Bernstein
94933bfebf
Don't label bindings-only changes with jit label (#18116) 2026-07-29 19:49:57 -04:00
Hiroshi SHIBATA
8495d0ecae win32/rm.bat: report removal failures via exit code
The `error` variable was set on failure but never reached the exit
code, so removal failures such as a locked DLL left `nmake clean`
green while stale artifacts remained.  Exit with `error` at the end.
Since `rd /s` does not set ERRORLEVEL on failure, check the entries
remaining after it instead.  Also pass `/f` to `del` to remove
read-only files as `rm -f` does.
2026-07-30 08:24:16 +09:00
Hiroshi SHIBATA
ba57693031 [ruby/rubygems] Fail bundle check when frozen mode needs lockfile changes
`BUNDLE_FROZEN=1 bundle check` reported that dependencies are satisfied and exited 0 even when the lockfile was missing CHECKSUMS entries, because `write_lock` only warns in frozen mode. Run the same frozen validation as `bundle install` so `bundle check` fails consistently.

https://github.com/ruby/rubygems/issues/9546

https://github.com/ruby/rubygems/commit/07337df092

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 23:23:51 +00:00
Kevin Newton
e67f02518a [ruby/prism] Pinned back references and pinned numbered references are not allowed
https://github.com/ruby/prism/commit/35c587b1c2
2026-07-29 20:59:02 +00:00
Peter Zhu
a69bb3ab60 Implement GC fast path for gen_new_range
Implements GC fast path for newrange instruction when we do not need to
call <=> method (when either endpoint is nil or both are fixnums).

Benchmark:

    def run(max)
      i = 0
      while i < max
        a = i..max
        i += 1
      end
    end

    30.times { run(10) }
    run(10_000_000)

Result:

    Benchmark 1: master
      Time (mean ± σ):     221.8 ms ±   5.2 ms    [User: 214.4 ms, System: 5.8 ms]
      Range (min … max):   216.4 ms … 235.8 ms    13 runs

    Benchmark 2: branch
      Time (mean ± σ):      62.4 ms ±   1.6 ms    [User: 56.0 ms, System: 5.1 ms]
      Range (min … max):    60.7 ms …  69.7 ms    46 runs

    Summary
      branch ran
        3.56 ± 0.12 times faster than master
2026-07-29 16:06:56 -04:00
Koichi Sasada
8ebf16fa73 id_table.c: keep managed id table live across foreach/dup callbacks
rb_managed_id_table stores its rb_id_table body as embedded data
(RUBY_TYPED_EMBEDDABLE), so managed_id_table_ptr returns a pointer
interior to the wrapper object.  rb_managed_id_table_foreach,
_foreach_values, and _dup hand that interior pointer to a callback that
may allocate and trigger a GC.  If the compiler stops keeping the
wrapper VALUE live once the interior pointer is taken, the table can be
reclaimed while it is still being iterated.  Add RB_GC_GUARD on the
wrapper across these calls.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 04:20:24 +09:00
Peter Zhu
1cbb55372a [ruby/mmtk] Skip tests in TestStruct
https://github.com/ruby/mmtk/commit/62fde9fc52
2026-07-29 19:02:55 +00:00
Peter Zhu
e0e96393b8 [ruby/mmtk] Skip tests in Test_StringCapacity
https://github.com/ruby/mmtk/commit/5767c1d970
2026-07-29 19:02:54 +00:00
Peter Zhu
d84e57fb38 [ruby/mmtk] Implement large object space
https://github.com/ruby/mmtk/commit/b458664cb1
2026-07-29 19:02:54 +00:00
Peter Zhu
3ae87b772b [ruby/mmtk] Remove MMTK_MAX_OBJ_SIZE
https://github.com/ruby/mmtk/commit/2f925b352a
2026-07-29 19:02:54 +00:00
Takashi Kokubun
20449aa178 ZJIT: Use TooManyArgsForLir reason for oversized direct block calls 2026-07-29 11:10:02 -07:00
Takashi Kokubun
6ea07a4824 Fix a comment about too many args
Co-authored-by: Kevin Menard <kevin@nirvdrum.com>
2026-07-29 11:10:02 -07:00
Takashi Kokubun
8b2bd6a500 ZJIT: Add HIR opt tests for oversized direct block calls 2026-07-29 11:10:02 -07:00
Takashi Kokubun
3731d6659f ZJIT: Reject direct block calls with too many args for LIR
The invokeblock fast path (InvokeBlockIseqDirect) passes captured self
plus each block argument in C argument registers, but
block_call_inlinable_iseq never bounded the argument count. Yielding 6+
arguments to a matching-arity block made gen_invoke_block_iseq_direct
emit a CCall with more operands than C_ARG_OPNDS, which aborts the
process in the backend:

  assertion failed: data.opnds.len() <= C_ARG_OPNDS.len()
  at zjit/src/backend/x86_64/mod.rs:302

Reject such blocks in block_call_inlinable_iseq so both the guarded and
the inlined-caller yield paths fall back to VM dispatch.
2026-07-29 11:10:02 -07:00
Takashi Kokubun
72749d7df3 ZJIT: Allowlist pointee types in PtrPrintMap::map_ptr
Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
2026-07-29 11:09:17 -07:00
Takashi Kokubun
ab191be2f5 ZJIT: Only accept raw pointers in PtrPrintMap::map_ptr
Previously map_ptr<T>() took *const T, so a reference like &IseqPtr
silently coerced to *const IseqPtr and mapped the address of the
reference itself instead of the pointer value, which is how the bug
fixed by the previous commit slipped in.

Constrain the parameter with an ActualPtr trait implemented only for
*const T and *mut T, so passing a reference no longer compiles.

Co-authored-by: Max Bernstein <tekknolagi@gmail.com>
2026-07-29 11:09:17 -07:00
Takashi Kokubun
821af045b6 ZJIT: Map pointer values instead of temporary addresses in HIR dump 2026-07-29 11:09:17 -07:00
Max Bernstein
9f6753e683 ZJIT: Use BitSet, not HashSet, for LIR block sets
No need for hashing in LIR RPO.
2026-07-29 14:05:23 -04:00
Max Bernstein
4a6e60e641 ZJIT: Rewrite LIR successors function to avoid cloning edges
We only need the target blocks, not the argument vectors.
2026-07-29 14:05:23 -04:00
Peter Zhu
5845220c37 Fix directory mappings for MMTk in sync_default_gems.rb 2026-07-29 13:16:51 -04:00
Koichi Sasada
a2cc692d77 Make Proc#refined's memoized iseq copy and memo shareable
Proc#refined memoizes {copied_iseq, cref} per source iseq in a
process-global map so the copy can be reused from any Ractor.  The
cref's refinements table is already frozen and marked shareable for
that reason, but the copied iseq and the memo array holding it are
left unshareable, so the shared payload is not actually a shareable
object graph.

Complete that intent: mark the copied iseq subtree shareable (it is
self-contained -- nested block iseqs are copied too) and mark the
frozen memo array shareable.  A copy's local_iseq/parent_iseq point at
already-shareable iseqs (other copies in the subtree, or the shareable
enclosing iseq), so the shareable check needs no special casing.
2026-07-30 01:34:00 +09:00
Peter Zhu
3ca77abb61 Drop MMTK_HEAP_MAX in test_oom
Sync ruby/mmtk@11ea3a437f.
2026-07-29 11:03:30 -04:00
himura467
d6e2feea47 Return a byte count from ractor_sync_memsize()
st_table_size() is the number of entries, but ractor_memsize() adds the result to sizeof(rb_ractor_t) as a byte count, so a Ractor was reported as costing one byte per open port. Use st_memsize(), as ractor_selector_memsize() in this file already does for its own ports table.
2026-07-29 16:56:28 +02:00
Max Bernstein
72cf6fff92
ZJIT: Make UnboxFixnum effectless (#18103)
It's just an integer right shift.
2026-07-29 14:42:03 +00:00
Max Bernstein
a32dded07d
ZJIT: Iterate over state in SetLocal (#18102)
We added state to `SetLocal` but didn't change `for_each_operand_impl`.
2026-07-29 10:09:51 -04:00
Nobuyoshi Nakada
6c2b11747a
Clarify strict-prototypes workaround [ci skip]
Document the affected Clang and Autoconf versions.
2026-07-29 19:52:42 +09:00
Jean Boussier
bb59337860 [ruby/json] Improve max_nesting documentation
https://github.com/ruby/json/commit/5499aa54a4
2026-07-29 07:48:43 +00:00
Patrick Linnane
b4e40f781a [ruby/rubygems] Document Bundler checksum behavior for default gems
Signed-off-by: Patrick Linnane <patrick@linnane.io>

https://github.com/ruby/rubygems/commit/56a82a2573
2026-07-29 05:06:48 +00:00
Hiroshi SHIBATA
0de0afb27c [ruby/rubygems] Assert on the lockfile in the cooldown bypass control spec
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>
2026-07-29 04:44:12 +00:00
Hiroshi SHIBATA
e86de2aa66 [ruby/rubygems] Pin cross-platform cooldown exclusion in an e2e spec
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>
2026-07-29 04:44:12 +00:00
Hiroshi SHIBATA
011591a069 [ruby/rubygems] Add --cooldown flag to bundle lock and bundle cache
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>
2026-07-29 04:44:11 +00:00
Nobuyoshi Nakada
2d616a9bb0
Remove unused autoload table copier
Remove the copier left unused when class fields cloning replaced raw
table copying in commit 3abdd4241fd5.
2026-07-29 11:42:03 +09:00
dependabot[bot]
dd1528212d Bump taiki-e/install-action
Bumps the github-actions group with 1 update in the / directory: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.85.2 to 2.85.3
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](41049aa566...18b1216eba)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.85.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 11:36:52 +09:00
Daichi Kamiyama
d13a49fc50
ZJIT: Fix kwargs handling in invokeblock ifunc specialization (#17971)
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.
2026-07-28 17:57:20 -07:00
Peter Zhu
2999c81ce2 Fix memory leak in MatchData
The following script leaks memory:

    10.times do
      # MatchData#offset allocates an external char_offset buffer
      100_000.times.map do
        m = /(a)(b)(c)(d)/.match("abcd")
        m.offset(0)
        m
      end

      GC.start

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

Before:

    49072
    56512
    66320
    72624
    80432
    89232
    96192
    105600
    111936
    121744

After:

    49328
    48928
    50864
    49312
    49248
    50160
    49264
    50816
    49296
    51216
2026-07-28 19:26:16 -04:00
Takashi Kokubun
cf81909161
Reapply "ZJIT: Add counters for throw and exception_handler" (#17945) (#18099)
This reverts commit ff6ac97529962b824d528b4bf32485179bb25805.
2026-07-28 14:31:02 -07:00
Luke Gruber
c05f55416f Remove flaky assertion in test_thread_timer_and_interrupt
Don't rely on exact timings even if they seem generous enough. This has
failed recently on CI (MacOS-15-intel).
2026-07-28 17:20:36 -04:00
Peter Zhu
120c8fa88c Fix memory leak when sweeping hashes with st_table
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
2026-07-28 15:56:48 -04:00
Kazuki Yamaguchi
7a4de4748a [ruby/openssl] pkcs7: fix flaky test test_enveloped
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
2026-07-28 19:26:48 +00:00
Luke Gruber
67eb41e051 Fix cross-ractor busy-waiting in vm_once_dispatch
Use a proper nogvl + native mutex broadcast solution to deal with
cross-ractor thread synchronization.
2026-07-28 14:54:50 -04:00
Luke Gruber
1a12d846b5 Fix once insn when multiple Ractors race to execute the iseq
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.
2026-07-28 14:54:50 -04:00
John Hawthorn
3ab7644a89 Add writebarrier for Array#reverse 2026-07-28 10:47:48 -07:00
Nozomi Hijikata
438674be00
ZJIT: Require trivial inline target to end at leave (#18063)
* 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>
2026-07-28 10:01:24 -07:00
himura467
d3aee406de Raise TypeError for uninitialized Ractor::Port
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]
2026-07-28 12:31:24 -04:00
Peter Zhu
c2473016ed Set STR_DUPLICATE_MAX_EMBED_LEN to 256
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)
2026-07-28 10:57:57 -04:00
Shizuo Fujita
9b76442b62
[DOC] Fix return value documentation of IO::Buffer#<=> and #hexdump (#18089)
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
2026-07-28 21:03:59 +09:00