2211 Commits
Author SHA1 Message Date
Peter Zhu 0b7838e23b Fix IO reading without GVL during GC compaction
[Bug #22467]

If we perform IO read without the GVL during GC compaction and the string
we are writing to is embedded, then it can get mprotect'd by the read
barrier in the GC, which can cause the IO read operation to fail.
2026-10-25 14:27:26 +09:00
Yusuke EndohandClaude Mythos 5.1 8074d3f475 Fix integer overflow in String#[]= with a capture index of INT_MIN
rb_str_subpat_set validated a negative capture index by negating it,
which overflows for INT_MIN and bypasses the range check. The
out-of-bounds read of the match registers then crashed the process.

Rewrite the check without negation.

Reported by: loki (https://hackerone.com/realnits)

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
2026-10-25 02:13:16 +09:00
BurdetteLamar 1d802519b7 [DOC] Make bitwise methods doc visible 2026-10-22 08:52:40 +09:00
Shourya Singh cfa50b0093 [DOC] Fix outdated results in examples
Some example results in the docs no longer match what Ruby prints:

* Array#flatten and Array#flatten!: Set#inspect now shows Set[6, 7]
* Module: Mod.constants also includes Math::DomainError
* Module#const_get and Module#const_set: Floats are shown with full
  precision
* Complex#imag: sin(PI/4) is 0.7071067811865475
* String#<<: missing # in #<Encoding:UTF-8>
* Enumerable#inject: typo #=>>
2026-10-21 17:52:28 +09:00
Burdette Lamar fa2fee5a1d [DOC] Tweaks for String#tr (#19433) 2026-10-21 11:17:32 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 4e746b14fd Make rb_len_t 64 bits on mswin
long is 32 bits on mswin while pointers are 64 bits, so String, Array and MatchData could not reach 2GiB there. config.h now sets SIZEOF_RB_LEN_T to 8 on mswin64, and value.h then defines rb_len_t as int64_t. mingw still uses long.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:07:39 +09:00
William T. NelsonandClaude Opus 5.5 965a08800c Fix termlen in String#initialize
Without capacity:, rb_enc_raw_set doesn't resize the buffer for a wider
terminator, so the terminator could overflow an embedded string.
rb_enc_associate resizes it.

With capacity: and no encoding:, the buffer was sized for a 1-byte
terminator even when orig or the receiver has a wide encoding.

Follow up 2d08b5f42f [Bug #22423]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:56:03 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 e63fe71ebf Check for RB_LEN_MIN before negating beg in rb_str_subpos
Negating RB_LEN_MIN overflows, so the compiler may assume `-beg < 0` never holds and drop the check. clang-cl does so, and `str[LONG_MIN, 1]` returns a substring instead of nil.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:01:04 +09:00
Erik Berlin 1bf9130943 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
2026-10-14 11:54:26 +09:00
Nobuyoshi Nakada 2d08b5f42f [Bug #22423] Preserve encoding with String capacity 2026-10-11 22:56:36 +09:00
Nobuyoshi Nakada 9cffa03aef Substite with NIL_OR_UNDEF_P more 2026-10-11 22:40:45 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 ee3d6c987b Use rb_len_t for the lengths and indices left in long
These still held String and Array lengths and indices in long, or cast them to long. rb_len_t is still long everywhere, so the generated code does not change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:45:25 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 d4a9221e64 Check the width of rb_len_t values against SIZEOF_RB_LEN_T
These range checks guard values that are now rb_len_t but still compared SIZEOF_LONG, so they would be compiled out once rb_len_t gets wider than long on LLP64. They compile the same while rb_len_t is long. ibf_dump_write takes the size as size_t so that its check sees the full value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Hiroshi SHIBATAandClaude Opus 5 d5ebdee531 Use rb_len_t for lengths and indices in string.c
RString capa becomes pointer-sized everywhere once rb_len_t widens, so fake strings can keep their precomputed hash on LLP64 as well.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Hiroshi SHIBATAandClaude Opus 5 cc3ba11012 Use rb_len_t in the C API for lengths and indices
Once rb_len_t widens this changes the `long *` output parameters of rb_range_beg_len(), rb_str_subpos(), rb_arithmetic_sequence_beg_len_step(), ruby_enc_find_basename() and ruby_enc_find_extname(), so extensions passing `long *` to them will need to be updated. The extension guides list these signatures verbatim and follow along.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Nobuyoshi Nakada 6c92d5b781 Release ALLOCV buffer
Release `ALLOCV` buffers promptly when they are no longer needed.
2026-10-02 15:03:17 +09:00
Peter Zhu 96d57e2403 Make rb_str_no_gvl_safe_acquire public C API
This commit renames rb_str_tmp_frozen_no_embed_acquire to rb_str_no_gvl_safe_acquire
and makes it part of the public C API.

There is currently no way for C extensions that use no GVL operations to
safely read from a string because it has no way of guaranteeing that the
string is not embedded. Embedded strings are not safe for no GVL operations
because GC compaction can run, which will mprotect the memory region and
will cause any read to SEGV.

See https://github.com/ruby/ruby/pull/9817 for when rb_str_tmp_frozen_no_embed_acquire
was introduced.
2026-10-01 10:02:46 +09:00
Randy Stauner 323c1df196 [Bug #22387] Fix termlen in str_shared_replace
The buffer is copied over based on the new string's termlen
so we can just set the new encoding raw instead of recalculating
based on the old encoding's termlen.
2026-09-30 21:49:17 +01:00
Peter Zhu 0093a0268f Fix race condition in rb_str_tmp_frozen_no_embed_acquire
[Bug #22382]

rb_str_tmp_frozen_no_embed_acquire is not Ractor safe for the non-embedded
case because it modifies the orig string. This can cause a race condition
if multiple Ractors are executing the same code path for the same string.
Since commit 9144c914a1, strings that are
pinned are not re-embedded, so we can safely return the original string
if it is frozen and not embedded.

The following script reproduces the crash:

    # frozen_string_literal: true
    require "tmpdir"

    dir = Dir.mktmpdir
    TRIES = 300

    1.upto(TRIES) do |try|
      $stderr.puts "try #{try} of #{TRIES}"
      str = Ractor.make_shareable(Random.bytes(512 * 1024))
      # Control: one prior write from a single Ractor prevents the crash.
      File.binwrite(File.join(dir, "first"), str) if ENV["WRITE_FIRST"]

      go = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 0.02
      12.times.map do |n|
        Ractor.new(str, File.join(dir, "w#{n}"), go) do |s, path, at|
          Thread.pass until Process.clock_gettime(Process::CLOCK_MONOTONIC) >= at # start together
          File.binwrite(path, s)
        end
      end.each(&:join)

      GC.start
    end

    puts "finished #{TRIES} tries without crashing"
2026-09-30 08:56:11 +09:00
Randy Stauner b58bea071d Fix buffer size accounting for rb_str_tmp_frozen roots
capa does not include the terminator, so when a buffer moves
between strings with different terminator lengths the capacity has to
be converted or we will try to free the buffer with the wrong size:

    [BUG] buffer 0x00005030039ff768 freed with old_size=18, but was allocated with size=17
    [BUG] buffer 0x00005631adc48f48 freed with old_size=8268, but was allocated with size=8269

Three related fixes:

* `rb_str_tmp_frozen_release`: convert tmp's capacity to orig's
  terminator length when handing the buffer back.  This is the inverse
  of a calculation added to `rb_str_tmp_frozen_no_embed_acquire` in
  91619f0230.

* `heap_str_make_shared`: since bdf3032e35
  the root created by `rb_str_tmp_frozen_acquire` does not copy the
  encoding of the string it takes the buffer from, so the two can have
  different terminator lengths and the root's capacity has to be
  adjusted as well, otherwise the root frees fewer bytes than were
  allocated once its only child stops sharing the buffer.  termlen is
  passed in because the caller assigns the encoding of the root after
  this function returns.

* `rb_str_tmp_frozen_no_embed_acquire`: unset `STR_BORROWED` on the
  root.  `STR_SET_SHARED` sets `STR_BORROWED` on roots with
  `klass == 0` to mark the ones that may have other children, but this
  root only ever has orig as its child, so unset it like
  `heap_str_make_shared` does.  Any string that starts sharing the
  root later will set the flag again.  Without this
  `rb_str_tmp_frozen_release` never gives the buffer back, so strings
  written by `io_fwrite` stay shared and are copied on their next
  modification.

The release and make_shared adjustments go together
as they were previously canceling each other out.
2026-09-26 21:24:18 +02:00
Randy Stauner 57213d44ce [Bug #22383] Fix inverted STR_SHARED check in rb_str_tmp_frozen_release
45a2c95d0f mistakenly rewrote

    FL_TEST_RAW(orig, STR_SHARED) && !FL_TEST_RAW(orig, STR_TMPLOCK|RUBY_FL_FREEZE)

as

    FL_TEST_RAW(orig, STR_SHARED | STR_TMPLOCK) == STR_TMPLOCK

requiring orig to not be shared, the opposite of the original
condition.  orig always shares the buffer with tmp here, so the buffer
was never given back and the string stayed shared until its next
modification copied it.  The code then read aux.shared from strings
that are not shared, where the union holds aux.capa.

This should be backported to 4.0
2026-09-25 11:03:48 -04:00
Peter Zhu 88bfa3005c Fix buffer overflow in String#rpartition
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 08:54:57 +09:00
Peter Zhu d4d0786729 Fix out-of-bounds access in String#byteindex/byterindex
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 15:29:59 +09:00
Peter Zhu 681d0f1425 Fix crash in String#tr when hash modified
String#tr can crash if keys are removed from the translation hash since
we stack allocate a buffer pairs. If entries are deleted during runtime,
then we won't fill the pairs buffer which can crash because it will be
reading uninitialized values out of pairs.

The following script demonstrates the crash:

    h = {}
    obj = Object.new
    obj.define_singleton_method(:to_str) do
      h.clear
      "a"
    end
    h[obj] = "x"
    ("b".."z").each { |c| h[c] = (c.ord + 1).chr }
    puts "ab".tr!(h)
2026-09-21 11:18:59 +09:00
Peter Zhu edce07a8c9 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-20 21:06:47 +09:00
Peter Zhu 5a4ccd91b6 Fix use-after-free in String#each_line with separator modified
The separator can be modified in the block yielded by String#each_line.
When that happens, the pointer can change which can cause an use-after-free.
For example, the following script crashes:

    sep = "x" * 1_000_000
    s = "a#{sep}b#{sep}c"
    s.each_line(sep) { sep.clear }
2026-09-20 10:21:03 +09:00
Peter Zhu 44874b7e7a Fix use-after-free in String#strip methods
The selector conversion may run aribtrary Ruby code and modify the source
string. This can cause a use-after-free if it resizes the source string.
For example, the following script causes corruption:

    s = "-" * 1000 + "abc" + "-" * 1000
    obj = Object.new
    obj.define_singleton_method(:to_str) do
      s.clear
      "-"
    end
    p s.strip(obj)
2026-09-19 21:28:43 +09:00
Jean Boussier 7b2c30d63a string.c: makes needles_count unsigned
Making `RBIMPL_ASSERT_OR_ASSUME(needles_count > 0);` redundant.
2026-09-18 10:02:29 +02:00
Jean Boussier f626f5523c string.c: use a const for the loop bound
GCC was emitting a warning, because it couldn't understand that
`search->needles_count` was assumed not to be 0:

```
string.c:9673:36: warning: ‘matches’ may be used uninitialized [-Wmaybe-uninitialized]
 9673 |                 const int bitmap = _mm_movemask_epi8(matches[0]);
      |                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
string.c: In function ‘tr_trans_pairs’:
string.c:9664:25: note: ‘matches’ declared here
 9664 |                 __m128i matches[TR_TRANS_PAIRS_SIMD_MAX_NEEDLES];
      |
```

Instead of adding some useless initialization (41c9fdd65c)
we can try copying the loop upper bound into a `const` so that the compiler
can understand it's never 0;
2026-09-18 10:02:29 +02:00
Peter Zhu ab5d140de8 Fix buffer overflow in String#slice! when string modified
The following script causes a buffer overflow and returns corrupted strings
because it's reading past the end of the string buffer:

    s = "x" + "l" * 3999
    obj = Object.new
    obj.define_singleton_method(:to_int) do
      s.clear
      0
    end
    p s.slice!(/l+$/, obj)

Outputs corrupted string that looks like:

    "\xEA\x94\xEA\xCE`\u0000\u0000`\x88n\xEFC
2026-09-18 14:29:05 +09:00
Peter Zhu 9a9f3827c5 Fix use-after-free in String#[]= when string modified
Fixes the following crash:

    str = "hello" * 100
    obj = Object.new
    obj.define_singleton_method(:to_str) do
      str.replace("")
      "x"
    end
    str[/h.l/] = obj
2026-09-18 13:15:33 +09:00
Jean Boussier 3d70677f51 string.c: fix off by one assertion 2026-09-17 19:33:30 +02:00
Peter Zhu 41c9fdd65c Fix warning in tr_trans_pairs_search_sse2
string.c:9673:36: warning: ‘matches’ may be used uninitialized [-Wmaybe-uninitialized]
 9673 |                 const int bitmap = _mm_movemask_epi8(matches[0]);
      |                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
string.c: In function ‘tr_trans_pairs’:
string.c:9664:25: note: ‘matches’ declared here
 9664 |                 __m128i matches[TR_TRANS_PAIRS_SIMD_MAX_NEEDLES];
      |
2026-09-17 19:36:42 +09:00
Peter Zhu 16abdbdf18 Fix out-of-bounds in String#setbyte when string modified
The call to rb_to_int may run aribtrary Ruby code and thus cause String#setbyte
to read past the end of the string. For example, the following script crashes:

    s = "a" * 10_000_000
    obj = Object.new
    obj.define_singleton_method(:to_int) do
      s.clear
      s.concat("x" * 50)
      0
    end
    s.setbyte(9_999_996, obj)
2026-09-17 10:19:47 +09:00
Nobuyoshi Nakada d8d75cee41 [Bug #22316] Check for frozen string before length checks
Warnings for chilled strings can be intercepted by `Warning` module.
2026-09-16 16:30:48 +09:00
HASUMI Hitoshi 03a426ec8d [Feature #22279] Add region arguments to String bit operations (#18562)
This patch extends the following String methods:

* String#bit_set(offset, length, lsb_first: true) -> self
* String#bit_set(range, lsb_first: true) -> self
* String#bit_clear(offset, length, lsb_first: true) -> self
* String#bit_clear(range, lsb_first: true) -> self
* String#bit_flip(offset, length, lsb_first: true) -> self
* String#bit_flip(range, lsb_first: true) -> self
* String#bit_count(offset, length, lsb_first: true) -> Integer
* String#bit_count(range, lsb_first: true) -> Integer

Link: [Feature #22279]
2026-09-14 17:30:15 +09:00
Jean Boussier 228f61397a string.c: Further String#tr fixes and refactors
* `tr_trans_pair` is now responsible for consuming the matches,
akin to the ERB change in 4ca48a192c.
This makes it impossible to shift by 64 or more, hence removes a
branch in `next_match`. Also ensure `s` and `matches_bitmap` members
stays in sync.

* Add missing `needles_count` check in `tr_trans_pairs_search_sse2`.

Reported-By: Ashley Allen <a.allen3@herts.ac.uk>
Reported-By: Asten Rooky
2026-09-14 09:05:49 +02:00
Nobuyoshi Nakada 81ab701fa1 Share common code to chomp a string by length
Extracted from `rb_str_chomp_string` and `rb_str_delete_suffix_bang`.
2026-09-13 00:06:54 +09:00
Jean BoussierandFederico Carrocera d5793e0b62 string.c: Fix multibyte replace in String#tr fast path
`tr_trans_pairs_search` expect the pointer to be incremented by
one only. Therefore, when matching a multibyte character, `search.s`
must be incremented by 1, and not `clen`.

Co-Authored-By: Federico Carrocera <fedecarrocera@gmail.com>
2026-09-11 21:25:34 +01:00
Jean Boussier 0adfd0ec77 string.c: Fix String#tr edge cases and improve coverage
[Feature #22238]

```ruby
"hello".tr("x" => "y")   #=> nil (should be "hello")
"hello".tr({})           #=> nil (should be "hello")

s = +"hello"
s.tr!("x" => "y")        #=> nil
s                        #=> "" (the receiver is emptied)

"0123456789abcdefghij".tr("h" => "H", "2" => "@")
```
2026-09-11 08:38:39 +01:00
Jean Boussier 4d646392e0 string.c: Fix UB in tr_trans_pairs_next_match_neon 2026-09-10 08:37:17 +01:00
Nobuyoshi Nakada 92ce852366 Revert "Win32: Add missing mklink internal command of cmd.exe"
This reverts commit a925c74bd2, which
includes commit miss.
2026-09-09 20:40:40 +09:00
Nobuyoshi Nakada a925c74bd2 Win32: Add missing mklink internal command of cmd.exe 2026-09-09 20:29:02 +09:00
Jean Boussier c0d6ffc72d string.c: Eliminate dead branch in tr_trans_pairs_next_match_* 2026-09-09 11:47:36 +01:00
Jean BoussierandFederico Carrocera 1d9a72944b string.c: Fix String#tr
`tr_trans_pairs_next_match` was expected to return the replacement
string not a boolean.

Co-Authored-By: Federico Carrocera <fedecarrocera@gmail.com>
2026-09-09 11:47:36 +01:00
Hiroshi SHIBATAandClaude Opus 5 be00aa83ba Do not look in front of a receiver shorter than one character in chomp
String#chomp steps back by the encoding's minimum character width before
testing for a newline, which reads before the first byte of the receiver
when it holds less than one whole character.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 13:54:26 +09:00
Hiroshi SHIBATAandClaude Opus 5 2cae75ab92 Fix the remaining rindex boundary checks
The same character count clamp reaches the backward scan in str_rindex() and
the pos == 0 shortcut in rb_str_rindex(), where a pattern with wider
characters than the receiver is still compared past its last byte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 13:54:26 +09:00
Nobuyoshi Nakada ac9cc92a49 Fix boundary check in rindex searching multibyte string 2026-09-08 13:54:26 +09:00
Nobuyoshi Nakada 4cf8d4f96e Fix lines at broken wchar string 2026-09-08 13:54:26 +09:00
Nobuyoshi Nakada eb9df91618 Fix boundary check at broken widechar string 2026-09-08 13:54:26 +09:00