From 5c683bd9b35212dc6d4970ca3a842e175ef0c203 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sun, 26 Oct 2025 14:04:16 -0500 Subject: [PATCH 1/4] [DOC] Tweaks for String#succ --- doc/string/succ.rdoc | 53 ++++++++++++++++++++++++++++++++++++++++++++ string.c | 52 +------------------------------------------ 2 files changed, 54 insertions(+), 51 deletions(-) create mode 100644 doc/string/succ.rdoc diff --git a/doc/string/succ.rdoc b/doc/string/succ.rdoc new file mode 100644 index 00000000000000..3653112b837e3c --- /dev/null +++ b/doc/string/succ.rdoc @@ -0,0 +1,53 @@ +Returns the successor to +self+. The successor is calculated by +incrementing characters. + +The first character to be incremented is the rightmost alphanumeric: +or, if no alphanumerics, the rightmost character: + + 'THX1138'.succ # => "THX1139" + '<>'.succ # => "<>" + '***'.succ # => '**+' + 'тест'.succ # => "тесу" + 'こんにちは'.succ # => "こんにちば" + +The successor to a digit is another digit, "carrying" to the next-left +character for a "rollover" from 9 to 0, and prepending another digit +if necessary: + + '00'.succ # => "01" + '09'.succ # => "10" + '99'.succ # => "100" + +The successor to a letter is another letter of the same case, +carrying to the next-left character for a rollover, +and prepending another same-case letter if necessary: + + 'aa'.succ # => "ab" + 'az'.succ # => "ba" + 'zz'.succ # => "aaa" + 'AA'.succ # => "AB" + 'AZ'.succ # => "BA" + 'ZZ'.succ # => "AAA" + +The successor to a non-alphanumeric character is the next character +in the underlying character set's collating sequence, +carrying to the next-left character for a rollover, +and prepending another character if necessary: + + s = 0.chr * 3 # => "\x00\x00\x00" + s.succ # => "\x00\x00\x01" + s = 255.chr * 3 # => "\xFF\xFF\xFF" + s.succ # => "\x01\x00\x00\x00" + +Carrying can occur between and among mixtures of alphanumeric characters: + + s = 'zz99zz99' # => "zz99zz99" + s.succ # => "aaa00aa00" + s = '99zz99zz' # => "99zz99zz" + s.succ # => "100aa00aa" + +The successor to an empty +String+ is a new empty +String+: + + ''.succ # => "" + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/string.c b/string.c index 89445df2445d9e..58fb70a8e5bb60 100644 --- a/string.c +++ b/string.c @@ -5315,57 +5315,7 @@ static VALUE str_succ(VALUE str); * call-seq: * succ -> new_str * - * Returns the successor to +self+. The successor is calculated by - * incrementing characters. - * - * The first character to be incremented is the rightmost alphanumeric: - * or, if no alphanumerics, the rightmost character: - * - * 'THX1138'.succ # => "THX1139" - * '<>'.succ # => "<>" - * '***'.succ # => '**+' - * - * The successor to a digit is another digit, "carrying" to the next-left - * character for a "rollover" from 9 to 0, and prepending another digit - * if necessary: - * - * '00'.succ # => "01" - * '09'.succ # => "10" - * '99'.succ # => "100" - * - * The successor to a letter is another letter of the same case, - * carrying to the next-left character for a rollover, - * and prepending another same-case letter if necessary: - * - * 'aa'.succ # => "ab" - * 'az'.succ # => "ba" - * 'zz'.succ # => "aaa" - * 'AA'.succ # => "AB" - * 'AZ'.succ # => "BA" - * 'ZZ'.succ # => "AAA" - * - * The successor to a non-alphanumeric character is the next character - * in the underlying character set's collating sequence, - * carrying to the next-left character for a rollover, - * and prepending another character if necessary: - * - * s = 0.chr * 3 - * s # => "\x00\x00\x00" - * s.succ # => "\x00\x00\x01" - * s = 255.chr * 3 - * s # => "\xFF\xFF\xFF" - * s.succ # => "\x01\x00\x00\x00" - * - * Carrying can occur between and among mixtures of alphanumeric characters: - * - * s = 'zz99zz99' - * s.succ # => "aaa00aa00" - * s = '99zz99zz' - * s.succ # => "100aa00aa" - * - * The successor to an empty +String+ is a new empty +String+: - * - * ''.succ # => "" + * :include: doc/string/succ.rdoc * */ From 9e49ee7937836875cc362fa4c948803e5da665c7 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 24 Oct 2025 23:24:17 +0100 Subject: [PATCH 2/4] [DOC] Tweaks for String#succ! --- string.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/string.c b/string.c index 58fb70a8e5bb60..b4585fd3205913 100644 --- a/string.c +++ b/string.c @@ -5420,7 +5420,9 @@ str_succ(VALUE str) * call-seq: * succ! -> self * - * Equivalent to String#succ, but modifies +self+ in place; returns +self+. + * Like String#succ, but modifies +self+ in place; returns +self+. + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE From b66fbd59ae5eb4ef994e0a1c007caaf8fbd3c897 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 26 Oct 2025 10:36:24 -0400 Subject: [PATCH 3/4] Make rb_vm_ccs_invalidate_and_free static --- vm_method.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm_method.c b/vm_method.c index 2cd41bd3774046..f5ad38e07c10fe 100644 --- a/vm_method.c +++ b/vm_method.c @@ -184,7 +184,7 @@ vm_ccs_invalidate(struct rb_class_cc_entries *ccs) } } -void +static void rb_vm_ccs_invalidate_and_free(struct rb_class_cc_entries *ccs) { RB_DEBUG_COUNTER_INC(ccs_free); From 52ea222027c7315a5d66f0d7b4ab73c1cc0c7344 Mon Sep 17 00:00:00 2001 From: Joshua Young Date: Sat, 25 Oct 2025 10:10:06 +0530 Subject: [PATCH 4/4] Fix segfault when moving nested objects between ractors during GC Fixes a segmentation fault when moving nested objects between ractors with GC stress enabled and YJIT. The issue is a timing problem: `move_enter` allocates new object shells but leaves their contents uninitialized until `move_leave` copies the actual data. If GC runs between these steps (which GC stress makes likely), it tries to follow what appear to be object pointers but are actually uninitialized memory, encountering null or invalid addresses. The fix zero-initializes the object contents immediately after allocation in `move_enter`, ensuring the GC finds safe null pointers instead of garbage data. The crash reproduced most consistently with nested hashes and YJIT, likely because nested structures create multiple uninitialized objects simultaneously while YJIT's memory usage increases the probability of GC triggering during moves. --- ractor.c | 4 +++- test/ruby/test_ractor.rb | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ractor.c b/ractor.c index 68ef0a87ac4cc7..ed0b023b1feec1 100644 --- a/ractor.c +++ b/ractor.c @@ -1940,8 +1940,10 @@ move_enter(VALUE obj, struct obj_traverse_replace_data *data) } else { VALUE type = RB_BUILTIN_TYPE(obj); + size_t slot_size = rb_gc_obj_slot_size(obj); type |= wb_protected_types[type] ? FL_WB_PROTECTED : 0; - NEWOBJ_OF(moved, struct RBasic, 0, type, rb_gc_obj_slot_size(obj), 0); + NEWOBJ_OF(moved, struct RBasic, 0, type, slot_size, 0); + MEMZERO(&moved[1], char, slot_size - sizeof(*moved)); data->replacement = (VALUE)moved; return traverse_cont; } diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index c4154cd2632b27..ccc551acef6192 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -99,6 +99,19 @@ def initialize(*) RUBY end + def test_move_nested_hash_during_gc_with_yjit + original_gc_stress = GC.stress + assert_ractor(<<~'RUBY', args: [{ "RUBY_YJIT_ENABLE" => "1" }]) + GC.stress = true + hash = { foo: { bar: "hello" }, baz: { qux: "there" } } + result = Ractor.new { Ractor.receive }.send(hash, move: true).value + assert_equal "hello", result[:foo][:bar] + assert_equal "there", result[:baz][:qux] + RUBY + ensure + GC.stress = original_gc_stress + end + def test_fork_raise_isolation_error assert_ractor(<<~'RUBY') ractor = Ractor.new do