From 0c9f3f1e68e23a9ba2d7fdfdb86bd7ddcaf5e9d9 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 16 Sep 2026 10:52:30 +0900 Subject: [PATCH 01/17] 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) --- array.c | 2 +- test/ruby/test_array.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/array.c b/array.c index 0d5430860aa719..802270551c9bc6 100644 --- a/array.c +++ b/array.c @@ -4844,7 +4844,7 @@ rb_ary_zip(int argc, VALUE *argv, VALUE ary) else { result = rb_ary_new_capa(len); - for (i=0; i Date: Wed, 16 Sep 2026 07:00:14 +0000 Subject: [PATCH 02/17] [DOC] Update bundled gems list at 0c9f3f1e68e23a9ba2d7fdfdb86bd7 --- NEWS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 4575b2bcbee7e9..2e33ba3960e001 100644 --- a/NEWS.md +++ b/NEWS.md @@ -185,7 +185,7 @@ They are still available on rubygems.org and can be installed with ### The following default gems are updated. * RubyGems 4.1.0.beta1 - * 4.0.3 to [v4.0.4][RubyGems-v4.0.4], [v4.0.5][RubyGems-v4.0.5], [v4.0.6][RubyGems-v4.0.6], [v4.0.7][RubyGems-v4.0.7], [v4.0.8][RubyGems-v4.0.8], [v4.0.9][RubyGems-v4.0.9], [v4.0.10][RubyGems-v4.0.10], [v4.0.11][RubyGems-v4.0.11], [v4.0.12][RubyGems-v4.0.12], [v4.0.13][RubyGems-v4.0.13], [v4.0.14][RubyGems-v4.0.14], [v4.0.15][RubyGems-v4.0.15], [v4.0.16][RubyGems-v4.0.16], [v4.0.17][RubyGems-v4.0.17], [v4.0.18][RubyGems-v4.0.18], [v4.0.19][RubyGems-v4.0.19], [v4.0.20][RubyGems-v4.0.20], [v4.1.0.beta1][RubyGems-v4.1.0.beta1] + * 4.0.3 to [v4.0.4][RubyGems-v4.0.4], [v4.0.5][RubyGems-v4.0.5], [v4.0.6][RubyGems-v4.0.6], [v4.0.7][RubyGems-v4.0.7], [v4.0.8][RubyGems-v4.0.8], [v4.0.9][RubyGems-v4.0.9], [v4.0.10][RubyGems-v4.0.10], [v4.0.11][RubyGems-v4.0.11], [v4.0.12][RubyGems-v4.0.12], [v4.0.13][RubyGems-v4.0.13], [v4.0.14][RubyGems-v4.0.14], [v4.0.15][RubyGems-v4.0.15], [v4.0.16][RubyGems-v4.0.16], [v4.0.17][RubyGems-v4.0.17], [v4.0.18][RubyGems-v4.0.18], [v4.0.19][RubyGems-v4.0.19], [v4.0.20][RubyGems-v4.0.20], [v4.0.21][RubyGems-v4.0.21], [v4.1.0.beta1][RubyGems-v4.1.0.beta1] * bundler 4.1.0.beta1 * 4.0.3 to [v4.0.4][bundler-v4.0.4], [v4.0.5][bundler-v4.0.5], [v4.0.6][bundler-v4.0.6], [v4.0.7][bundler-v4.0.7], [v4.0.8][bundler-v4.0.8], [v4.0.9][bundler-v4.0.9], [v4.0.10][bundler-v4.0.10], [v4.0.11][bundler-v4.0.11], [v4.0.12][bundler-v4.0.12], [v4.0.13][bundler-v4.0.13], [v4.0.14][bundler-v4.0.14], [v4.0.15][bundler-v4.0.15], [v4.0.16][bundler-v4.0.16], [v4.0.17][bundler-v4.0.17] * erb 6.0.7 @@ -510,6 +510,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [RubyGems-v4.0.18]: https://github.com/rubygems/rubygems/releases/tag/v4.0.18 [RubyGems-v4.0.19]: https://github.com/rubygems/rubygems/releases/tag/v4.0.19 [RubyGems-v4.0.20]: https://github.com/rubygems/rubygems/releases/tag/v4.0.20 +[RubyGems-v4.0.21]: https://github.com/rubygems/rubygems/releases/tag/v4.0.21 [RubyGems-v4.1.0.beta1]: https://github.com/rubygems/rubygems/releases/tag/v4.1.0.beta1 [bundler-v4.0.4]: https://github.com/rubygems/rubygems/releases/tag/bundler-v4.0.4 [bundler-v4.0.5]: https://github.com/rubygems/rubygems/releases/tag/bundler-v4.0.5 From d8d75cee41c83e07ef2bd2d616255803617b0700 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 16 Sep 2026 15:03:52 +0900 Subject: [PATCH 03/17] [Bug #22316] Check for frozen string before length checks Warnings for chilled strings can be intercepted by `Warning` module. --- spec/ruby/core/string/bit_clear_spec.rb | 10 ++-- spec/ruby/core/string/bit_flip_spec.rb | 10 ++-- spec/ruby/core/string/bit_set_spec.rb | 10 ++-- string.c | 8 ++- test/ruby/test_string.rb | 75 ++++++++++++++++++++++++- 5 files changed, 95 insertions(+), 18 deletions(-) diff --git a/spec/ruby/core/string/bit_clear_spec.rb b/spec/ruby/core/string/bit_clear_spec.rb index 1a73167a7f5e70..2cfa529e5e91a9 100644 --- a/spec/ruby/core/string/bit_clear_spec.rb +++ b/spec/ruby/core/string/bit_clear_spec.rb @@ -34,17 +34,17 @@ end it "raises an IndexError for an out of range bit offset" do - -> { "\x00".bit_clear(8) }.should.raise(IndexError) - -> { "\x00".bit_clear(-1) }.should.raise(IndexError) + -> { (+"\x00").bit_clear(8) }.should.raise(IndexError) + -> { (+"\x00").bit_clear(-1) }.should.raise(IndexError) end it "raises an IndexError when a region extends past the end" do - -> { "\x00".bit_clear(0, 9) }.should.raise(IndexError) - -> { "\x00".bit_clear(0..8) }.should.raise(IndexError) + -> { (+"\x00").bit_clear(0, 9) }.should.raise(IndexError) + -> { (+"\x00").bit_clear(0..8) }.should.raise(IndexError) end it "raises an ArgumentError for a negative length" do - -> { "\x00".bit_clear(0, -1) }.should.raise(ArgumentError) + -> { (+"\x00").bit_clear(0, -1) }.should.raise(ArgumentError) end it "raises a FrozenError if self is frozen" do diff --git a/spec/ruby/core/string/bit_flip_spec.rb b/spec/ruby/core/string/bit_flip_spec.rb index 9bf3136f2e168f..27fee9de4e3bfc 100644 --- a/spec/ruby/core/string/bit_flip_spec.rb +++ b/spec/ruby/core/string/bit_flip_spec.rb @@ -36,17 +36,17 @@ end it "raises an IndexError for an out of range bit offset" do - -> { "\x00".bit_flip(8) }.should.raise(IndexError) - -> { "\x00".bit_flip(-1) }.should.raise(IndexError) + -> { (+"\x00").bit_flip(8) }.should.raise(IndexError) + -> { (+"\x00").bit_flip(-1) }.should.raise(IndexError) end it "raises an IndexError when a region extends past the end" do - -> { "\x00".bit_flip(0, 9) }.should.raise(IndexError) - -> { "\x00".bit_flip(0..8) }.should.raise(IndexError) + -> { (+"\x00").bit_flip(0, 9) }.should.raise(IndexError) + -> { (+"\x00").bit_flip(0..8) }.should.raise(IndexError) end it "raises an ArgumentError for a negative length" do - -> { "\x00".bit_flip(0, -1) }.should.raise(ArgumentError) + -> { (+"\x00").bit_flip(0, -1) }.should.raise(ArgumentError) end it "raises a FrozenError if self is frozen" do diff --git a/spec/ruby/core/string/bit_set_spec.rb b/spec/ruby/core/string/bit_set_spec.rb index 6059c9f2459d27..4764342f630efc 100644 --- a/spec/ruby/core/string/bit_set_spec.rb +++ b/spec/ruby/core/string/bit_set_spec.rb @@ -34,17 +34,17 @@ end it "raises an IndexError for an out of range bit offset" do - -> { "\x00".bit_set(8) }.should.raise(IndexError) - -> { "\x00".bit_set(-1) }.should.raise(IndexError) + -> { (+"\x00").bit_set(8) }.should.raise(IndexError) + -> { (+"\x00").bit_set(-1) }.should.raise(IndexError) end it "raises an IndexError when a region extends past the end" do - -> { "\x00".bit_set(0, 9) }.should.raise(IndexError) - -> { "\x00".bit_set(0..8) }.should.raise(IndexError) + -> { (+"\x00").bit_set(0, 9) }.should.raise(IndexError) + -> { (+"\x00").bit_set(0..8) }.should.raise(IndexError) end it "raises an ArgumentError for a negative length" do - -> { "\x00".bit_set(0, -1) }.should.raise(ArgumentError) + -> { (+"\x00").bit_set(0, -1) }.should.raise(ArgumentError) end it "raises a FrozenError if self is frozen" do diff --git a/string.c b/string.c index 641553b8e3594a..bff524bd8a2f7f 100644 --- a/string.c +++ b/string.c @@ -7156,6 +7156,8 @@ str_mutate_single_bit(VALUE str, VALUE index, bool lsb_first, enum str_bit_mutat unsigned char *ptr; unsigned char mask; + rb_check_frozen(str); + if (str_bit_offset_out_of_range(RSTRING_LEN(str), offset.value)) { rb_raise(rb_eIndexError, "bit index out of range"); } @@ -7204,6 +7206,9 @@ str_mutate_bit(int argc, VALUE *argv, VALUE str, enum str_bit_mutation mutation) len = str_bit_length_from_index(length_v); } + /* Even a zero-length write requires a mutable receiver. */ + rb_check_frozen(str); + /* * A region that begins past the end is out of range even when it is * empty, and one that runs past the end is not allowed to silently @@ -7222,8 +7227,7 @@ str_mutate_bit(int argc, VALUE *argv, VALUE str, enum str_bit_mutation mutation) rb_raise(rb_eIndexError, "bit range out of range"); } } - /* Even a zero-length write requires a mutable receiver. */ - rb_check_frozen(str); + if (len == 0) return str; rb_str_modify(str); diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 899ff08692aacb..4a7e8699d9d75b 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -1201,7 +1201,7 @@ def test_bit_set_clear_flip_region assert_raise(FrozenError) { S("\x00").freeze.bit_set(0, 0) } assert_raise(FrozenError) { S("\x00").freeze.bit_clear(0...0) } assert_raise(FrozenError) { S("\x00").freeze.bit_flip(8..) } - assert_raise(IndexError) { S("\x00").freeze.bit_set(9, 0) } + assert_raise(FrozenError) { S("\x00").freeze.bit_set(9, 0) } # Copy-on-write: mutating must not affect a shared sibling. shared = S("fooXbar").split(S("X")).last @@ -1209,6 +1209,79 @@ def test_bit_set_clear_flip_region assert_equal(S("\xFFar").b, shared.b) end + def test_bit_set_single_chilled_string_warning + return unless @cls == String + assert_bit_op_chilled_string_warning(:bit_set, 7000 * 8) + end + + def test_bit_clear_single_chilled_string_warning + return unless @cls == String + assert_bit_op_chilled_string_warning(:bit_clear, 7000 * 8) + end + + def test_bit_flip_single_chilled_string_warning + return unless @cls == String + assert_bit_op_chilled_string_warning(:bit_flip, 7000 * 8) + end + + def test_bit_set_region_chilled_string_warning + return unless @cls == String + assert_bit_op_chilled_string_warning(:bit_set, 7000 * 8, 2) + end + + def test_bit_clear_region_chilled_string_warning + return unless @cls == String + assert_bit_op_chilled_string_warning(:bit_clear, 7000 * 8, 2) + end + + def test_bit_flip_region_chilled_string_warning + return unless @cls == String + assert_bit_op_chilled_string_warning(:bit_flip, 7000 * 8, 2) + end + + def test_bit_set_range_chilled_string_warning + return unless @cls == String + assert_bit_op_chilled_string_warning(:bit_set, (7000 * 8)..(7100 * 8)) + end + + def test_bit_clear_range_chilled_string_warning + return unless @cls == String + assert_bit_op_chilled_string_warning(:bit_clear, (7000 * 8)..(7100 * 8)) + end + + def test_bit_flip_range_chilled_string_warning + return unless @cls == String + assert_bit_op_chilled_string_warning(:bit_flip, (7000 * 8)..(7100 * 8)) + end + + def assert_bit_op_chilled_string_warning(op, *args) + assert_separately([], "#{<<-"{#"}\n#{<<-'};'}") + $target = <<~STR # chilled string + #{("A"*63+"\n")*128} # 64*128 = 8192 + STR + END { + assert_raise(IndexError) {$target.#{op}(#{args.map(&:inspect).join(", ")})} + } + {# + $reentered = false + + module ReallocateWarning + def warn(message, category: nil, **kwargs) + if category == :deprecated && !$reentered + $reentered = true + $target.clear + $target << ("B" * 1024) + return nil + end + + super + end + end + + Warning.extend(ReallocateWarning) + }; + end + def test_bit_count assert_equal(0, S("").bit_count) assert_equal(0, S("\x00").bit_count) From 1e16b651ca2df7eb850f57b65bb87184f9376d73 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 16 Sep 2026 11:36:01 +0900 Subject: [PATCH 04/17] Remove the unused `ptr` in `moreswitches` It held the base pointer for `ruby_xfree(ptr)` while the loop advanced `argv`. The free became `RB_ALLOCV_END(ptr_obj)` in da4bd3b3df9f, and nothing has read it since. Co-Authored-By: Claude Opus 5 --- ruby.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ruby.c b/ruby.c index 0f8f1e3db90563..9fb594a139a38a 100644 --- a/ruby.c +++ b/ruby.c @@ -898,7 +898,6 @@ moreswitches(const char *s, ruby_cmdline_options_t *opt, int envopt) char **argv, *p; const char *ap = 0; VALUE argstr, argary; - void *ptr; VALUE src_enc_name = opt->src.enc.name; VALUE ext_enc_name = opt->ext.enc.name; @@ -935,7 +934,7 @@ moreswitches(const char *s, ruby_cmdline_options_t *opt, int envopt) rb_str_cat(argary, (char *)&ap, sizeof(ap)); VALUE ptr_obj; - argv = ptr = RB_ALLOCV_N(char *, ptr_obj, argc); + argv = RB_ALLOCV_N(char *, ptr_obj, argc); MEMMOVE(argv, RSTRING_PTR(argary), char *, argc); while ((i = proc_options(argc, argv, opt, envopt)) > 1 && envopt && (argc -= i) > 0) { From d59deed82de07347bf1bb4acba89f2855e193c75 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 16 Sep 2026 17:54:08 +0900 Subject: [PATCH 05/17] Skip the prerelease version check for test-unit-ruby-core Only `lib` and `test` are synced from ruby/test-unit-ruby-core, so no gemspec exists in this tree and `Gem::Specification.load(nil)` returned nil. Co-Authored-By: Claude Opus 5 --- tool/sync_default_gems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 2232f5ac88d87b..1c395270a810be 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -464,7 +464,7 @@ def sync_default_gems(gem) end def check_prerelease_version(gem) - return if ["rubygems", "mmtk", "Onigmo"].include?(gem) + return if ["rubygems", "mmtk", "Onigmo", "test-unit-ruby-core"].include?(gem) require "net/https" require "json" From fe24147a6224399a611826ad5beedf34e48cc18c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 26 Aug 2026 16:03:50 +0900 Subject: [PATCH 06/17] [ruby/resolv] Do not register on-demand classes for unknown DNS types and SvcParamKeys Decoding an unknown (type, class) pair or an unknown SvcParamKey registered the generated class in a constant and in ClassHash permanently, so a malicious response could exhaust memory. Dropping the registration means a fresh class per decode, which breaks the class identity that Resource#== and Message#== relied on, so both now compare through Resource::Generic.type_class_equal?. Fixes CVE-2026-80212. https://github.com/ruby/resolv/commit/9e331b5960 --- test/resolv/test_resource.rb | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/resolv/test_resource.rb b/test/resolv/test_resource.rb index c5d22ec0e6419b..aa90a5c00ada08 100644 --- a/test/resolv/test_resource.rb +++ b/test/resolv/test_resource.rb @@ -81,6 +81,67 @@ def test_generic_question_equality Resolv::DNS::Message.decode(wire).answer.first[2] end + # Decoding an unknown (type, class) pair builds a fresh class every time, so + # equality must not rest on the class identity. + def test_generic_equality + wire = generic_answer(40000, "\x01\x02\x03") + rr1 = decode_generic(wire) + rr2 = decode_generic(wire) + + assert_not_same rr1.class, rr2.class + assert_equal rr1, rr2 + assert rr1.eql?(rr2) + assert_equal rr1.hash, rr2.hash + assert_equal Resolv::DNS::Message.decode(wire), Resolv::DNS::Message.decode(wire) + end + + # Any descendant counts, not just a class create returned. + def test_generic_equality_between_descendants + generic = Resolv::DNS::Resource::Generic + direct = generic.create(40000, 60000) + descendant = Class.new(generic.create(40000, 60000)) + + assert_equal direct.new("\x01\x02\x03"), descendant.new("\x01\x02\x03") + assert_equal descendant.new("\x01\x02\x03"), direct.new("\x01\x02\x03") + assert_equal generic.new("\x01\x02\x03"), generic.new("\x01\x02\x03") + assert_not_equal direct.new("\x01\x02\x03"), + Class.new(generic.create(40001, 60000)).new("\x01\x02\x03") + end + + def test_generic_inequality + rr = decode_generic(generic_answer(40000, "\x01\x02\x03")) + + assert_not_equal rr, decode_generic(generic_answer(40001, "\x01\x02\x03")) + assert_not_equal rr, decode_generic(generic_answer(40000, "\x09\x09\x09")) + assert_not_equal rr, Resolv::DNS::Resource::IN::A.new("192.168.0.1") + end + + # A question holds the resource class itself, so it needs the same treatment. + def test_generic_question_equality + wire = generic_question(40000) + + assert_equal Resolv::DNS::Message.decode(wire), Resolv::DNS::Message.decode(wire) + assert_not_equal Resolv::DNS::Message.decode(wire), + Resolv::DNS::Message.decode(generic_question(40001)) + end + + private def header(qdcount, ancount) + "\x00\x00\x00\x00".b + [qdcount, ancount, 0, 0].pack('nnnn') + end + + private def generic_answer(type, rdata) + rdata = rdata.b + (header(0, 1) + "\x00".b + [type, 60000, 0, rdata.bytesize].pack('nnNn') + rdata).b + end + + private def generic_question(type) + (header(1, 0) + "\x07example\x03com\x00".b + [type, 60000].pack('nn')).b + end + + private def decode_generic(wire) + Resolv::DNS::Message.decode(wire).answer.first[2] + end + def test_srv_no_compress # Domain name in SRV RDATA should not be compressed issue29 = 'https://github.com/ruby/resolv/issues/29' From 20a24c00e31677c4b057d44a373552eed943d738 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 26 Aug 2026 16:04:05 +0900 Subject: [PATCH 07/17] [ruby/resolv] v0.7.2 https://github.com/ruby/resolv/commit/67db06d75c --- lib/resolv.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index 07a39f0cf409ea..f2162f709b93fa 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -35,7 +35,7 @@ class Resolv # The version string - VERSION = "0.7.1" + VERSION = "0.7.2" ## # Looks up the first IP address for +name+. From 0c579be49b0b830de80ae14d3639e532ac53385c Mon Sep 17 00:00:00 2001 From: git Date: Wed, 16 Sep 2026 09:28:55 +0000 Subject: [PATCH 08/17] Update default gems list at 20a24c00e31677c4b057d44a373552 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 2e33ba3960e001..7b89807d0c9fc8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -208,7 +208,7 @@ They are still available on rubygems.org and can be installed with * 1.7.0 to [v1.8.0][prism-v1.8.0], [v1.8.1][prism-v1.8.1], [v1.9.0][prism-v1.9.0] * psych 5.5.0 * 5.3.1 to [v5.4.0][psych-v5.4.0], [v5.5.0][psych-v5.5.0] -* resolv 0.7.1 +* resolv 0.7.2 * 0.7.0 to [v0.7.1][resolv-v0.7.1] * stringio 3.2.1.dev * strscan 3.1.9.dev From 50e414bbc5c6336f094a75de8d5841a875290e4c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 16 Sep 2026 18:06:07 +0900 Subject: [PATCH 09/17] Map each file of ruby/test-unit-ruby-core instead of the directories Syncing starts by `rm_rf`-ing every destination, so the `lib` and `test` directory mappings wiped ruby/ruby own files under `tool/lib` and `tool/test`, leaving only the eight files the upstream has. They also made `Repository.find_upstream` claim both directories whole. Co-Authored-By: Claude Opus 5 --- tool/sync_default_gems.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 1c395270a810be..93eced9491152b 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -304,9 +304,17 @@ def lib((upstream, branch), gemspec_in_subdir: false) ["test/zlib", "test/zlib"], ["zlib.gemspec", "ext/zlib/zlib.gemspec"], ]), + # Most files under tool/lib and tool/test belong to ruby/ruby, so map + # each upstream file rather than the directories. "test-unit-ruby-core":repo("ruby/test-unit-ruby-core", [ - ["lib", "tool/lib"], - ["test", "tool/test"], + ["lib/core_assertions.rb", "tool/lib/core_assertions.rb"], + ["lib/envutil.rb", "tool/lib/envutil.rb"], + ["lib/find_executable.rb", "tool/lib/find_executable.rb"], + ["lib/memory_status.rb", "tool/lib/memory_status.rb"], + ["test/test_core_assertions.rb", "tool/test/test_core_assertions.rb"], + ["test/test_envutil.rb", "tool/test/test_envutil.rb"], + ["test/test_find_executable.rb", "tool/test/test_find_executable.rb"], + ["test/test_memory_status.rb", "tool/test/test_memory_status.rb"], ]), }.transform_keys(&:to_s) From 4e029dfbac0650802d2e80729e1933c4d4101707 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 16 Sep 2026 18:24:13 +0900 Subject: [PATCH 10/17] Mark an unreleased version as a prerelease when syncing Upstream bumps the version right after a release, so a synced tree usually holds a version nobody can install, and `bundle install` against ruby-dev locks to it. `check_prerelease_version` already knows the latest released version, so append `.dev` there when the tree is ahead of it. Co-Authored-By: Claude Opus 5 --- tool/sync_default_gems.rb | 36 ++++++++++++++++++++++++++++- tool/test/test_sync_default_gems.rb | 17 ++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 93eced9491152b..0799a3d10cfa0a 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -492,7 +492,41 @@ def check_prerelease_version(gem) "lib/#{gem.split("-").join("/")}/#{gem}.gemspec", ].find{|gemspec| File.exist?(gemspec)} spec = Gem::Specification.load(gemspec) - puts "#{gem}-#{spec.version} is not latest version of rubygems.org" if spec.version.to_s != latest_version + version = spec.version + return if version.to_s == latest_version + + if !version.prerelease? and Gem::Version.correct?(latest_version) and + version > Gem::Version.new(latest_version) and + prerelease = mark_as_prerelease(gem, version) + puts "#{gem}-#{version} is not released yet, marked as #{prerelease}" + else + puts "#{gem}-#{version} is not latest version of rubygems.org" + end + end + + # Upstream bumps the version just after a release, so the synced tree holds a + # version nobody can install yet, and `bundle install` against it would lock + # to that version. Turn it into a prerelease instead. + def mark_as_prerelease(gem, version) + literal = %["#{version}"] + files = REPOSITORIES[gem].mappings.flat_map do |_src, dst| + File.directory?(dst) ? Dir.glob("#{dst}/**/*.{c,rb,gemspec}") : [dst] + end + found = files.uniq.filter_map do |file| + next unless File.file?(file) + source = File.binread(file) + definition = source.lines.find {|line| line.include?(literal) and line.include?("VERSION")} + [file, source, definition] if definition + end + unless found.size == 1 + puts "Cannot tell where #{gem}-#{version} is defined" + return nil + end + + file, source, definition = found[0] + prerelease = "#{version}.dev" + File.binwrite(file, source.sub(definition) {definition.sub(literal) {%["#{prerelease}"]}}) + prerelease end def message_filter(repo, sha, log, context: nil) diff --git a/tool/test/test_sync_default_gems.rb b/tool/test/test_sync_default_gems.rb index cf65173c3e887d..8f82dac1fcbd84 100755 --- a/tool/test/test_sync_default_gems.rb +++ b/tool/test/test_sync_default_gems.rb @@ -252,6 +252,23 @@ def test_minimize_dependencies MAKE end + def test_mark_as_prerelease + File.write("src/lib/common.rb", %[VERSION = "1.2.3"\n]) + Dir.chdir("src") do + assert_equal("1.2.3.dev", SyncDefaultGems.mark_as_prerelease(@target, Gem::Version.new("1.2.3"))) + end + assert_equal(%[VERSION = "1.2.3.dev"\n], File.read("src/lib/common.rb")) + end + + def test_mark_as_prerelease_undefined + out = capture_process_output_to([STDOUT]) do + Dir.chdir("src") do + assert_nil(SyncDefaultGems.mark_as_prerelease(@target, Gem::Version.new("1.2.3"))) + end + end + assert_match(/Cannot tell where/, out) + end + def test_unknown_repository assert_raise_with_message(RuntimeError, /unknown/) do SyncDefaultGems::REPOSITORIES["not-exist"] From f85f15293ed339d3bb39fae8fd16b6fdfd1a7b75 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 14 Sep 2026 16:15:40 +0900 Subject: [PATCH 11/17] [ruby/io-console] Skip unavailable win32 virtual key generation rules Avoid gem build failures when `gperf` is installed because the gem omits `win32_vk.mk`. https://github.com/ruby/io-console/commit/3813a43247 --- ext/io/console/extconf.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/io/console/extconf.rb b/ext/io/console/extconf.rb index 65d3605762f530..6837d48f6bd4ff 100644 --- a/ext/io/console/extconf.rb +++ b/ext/io/console/extconf.rb @@ -61,11 +61,11 @@ console.#$OBJEXT: $(VK_HEADER) MK end - if vk_tool + if vk_tool and vk_mk = (File.read("#$srcdir/win32_vk.mk") rescue nil) unless conf.any? {|c| /^ *top_srcdir *=/.match?(c)} conf << "top_srcdir = $(srcdir)/../../..\n" end - conf.concat(depend_rules(File.read("#$srcdir/win32_vk.mk"))) + conf.concat(depend_rules(vk_mk)) end conf } From bca8e0d15a0420cc61fdbab8d7cf5172160ceab6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 14 Sep 2026 20:05:03 +0900 Subject: [PATCH 12/17] [ruby/io-console] bump up to 0.9.3 https://github.com/ruby/io-console/commit/fbac53dbe8 --- ext/io/console/console.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/io/console/console.c b/ext/io/console/console.c index d3f0a7d86041d0..aa19901a72825f 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -4,7 +4,7 @@ */ static const char *const -IO_CONSOLE_VERSION = "0.9.2"; +IO_CONSOLE_VERSION = "0.9.3"; #include "ruby.h" #include "ruby/io.h" From 30ded5b167fbba0713339a6f3bae03b2f956d4f9 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 16 Sep 2026 18:40:35 +0900 Subject: [PATCH 13/17] Rewrite rdoc-ref from absolute path at stringio doc --- doc/stringio/gets.rdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/stringio/gets.rdoc b/doc/stringio/gets.rdoc index 4152152a25239e..053553e4d39edd 100644 --- a/doc/stringio/gets.rdoc +++ b/doc/stringio/gets.rdoc @@ -76,7 +76,7 @@ The position need not be at a character boundary: Special Record Separators Like some methods in class IO, method +gets+ honors two special record separators; -see {Special Line Separators}[https://docs.ruby-lang.org/en/master/IO.html#class-IO-label-Special+Line+Separator+Values]: +see {Special Line Separators}[rdoc-ref:IO@Special+Line+Separator+Values]: strio = StringIO.new(TEXT) strio.gets('') # Read "paragraph" (up to empty line). From caa446249020d0ed3f8cbc1ecde68818bf0de595 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 16 Sep 2026 09:41:49 +0000 Subject: [PATCH 14/17] Update default gems list at 30ded5b167fbba0713339a6f3bae03 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 7b89807d0c9fc8..03f45a4bfb148b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -191,7 +191,7 @@ They are still available on rubygems.org and can be installed with * erb 6.0.7 * 6.0.1 to [v6.0.1.1][erb-v6.0.1.1], [v6.0.2][erb-v6.0.2], [v6.0.3][erb-v6.0.3], [v6.0.4][erb-v6.0.4], [v6.0.5][erb-v6.0.5], [v6.0.6][erb-v6.0.6], [v6.0.7][erb-v6.0.7] * error_highlight 0.7.2 -* io-console 0.9.2 +* io-console 0.9.3 * 0.8.2 to [v0.9.0][io-console-v0.9.0], [v0.9.1][io-console-v0.9.1], [v0.9.2][io-console-v0.9.2] * io-wait 999.999.999 * ipaddr 1.2.9 From 226f37012d4bd6b998e3b59084f206b82fb89cee Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 16 Sep 2026 19:39:20 +0900 Subject: [PATCH 15/17] [ruby/resolv] Revert "Do not register on-demand classes for unknown DNS types and SvcParamKeys" This reverts commit https://github.com/ruby/resolv/commit/9e331b5960a8, which contains only the same test code as previous two commits; https://github.com/ruby/resolv/commit/05185b43229f and https://github.com/ruby/resolv/commit/fcff0a19e401 https://github.com/ruby/resolv/commit/90fa897e28 --- test/resolv/test_resource.rb | 61 ------------------------------------ 1 file changed, 61 deletions(-) diff --git a/test/resolv/test_resource.rb b/test/resolv/test_resource.rb index aa90a5c00ada08..c5d22ec0e6419b 100644 --- a/test/resolv/test_resource.rb +++ b/test/resolv/test_resource.rb @@ -81,67 +81,6 @@ def test_generic_question_equality Resolv::DNS::Message.decode(wire).answer.first[2] end - # Decoding an unknown (type, class) pair builds a fresh class every time, so - # equality must not rest on the class identity. - def test_generic_equality - wire = generic_answer(40000, "\x01\x02\x03") - rr1 = decode_generic(wire) - rr2 = decode_generic(wire) - - assert_not_same rr1.class, rr2.class - assert_equal rr1, rr2 - assert rr1.eql?(rr2) - assert_equal rr1.hash, rr2.hash - assert_equal Resolv::DNS::Message.decode(wire), Resolv::DNS::Message.decode(wire) - end - - # Any descendant counts, not just a class create returned. - def test_generic_equality_between_descendants - generic = Resolv::DNS::Resource::Generic - direct = generic.create(40000, 60000) - descendant = Class.new(generic.create(40000, 60000)) - - assert_equal direct.new("\x01\x02\x03"), descendant.new("\x01\x02\x03") - assert_equal descendant.new("\x01\x02\x03"), direct.new("\x01\x02\x03") - assert_equal generic.new("\x01\x02\x03"), generic.new("\x01\x02\x03") - assert_not_equal direct.new("\x01\x02\x03"), - Class.new(generic.create(40001, 60000)).new("\x01\x02\x03") - end - - def test_generic_inequality - rr = decode_generic(generic_answer(40000, "\x01\x02\x03")) - - assert_not_equal rr, decode_generic(generic_answer(40001, "\x01\x02\x03")) - assert_not_equal rr, decode_generic(generic_answer(40000, "\x09\x09\x09")) - assert_not_equal rr, Resolv::DNS::Resource::IN::A.new("192.168.0.1") - end - - # A question holds the resource class itself, so it needs the same treatment. - def test_generic_question_equality - wire = generic_question(40000) - - assert_equal Resolv::DNS::Message.decode(wire), Resolv::DNS::Message.decode(wire) - assert_not_equal Resolv::DNS::Message.decode(wire), - Resolv::DNS::Message.decode(generic_question(40001)) - end - - private def header(qdcount, ancount) - "\x00\x00\x00\x00".b + [qdcount, ancount, 0, 0].pack('nnnn') - end - - private def generic_answer(type, rdata) - rdata = rdata.b - (header(0, 1) + "\x00".b + [type, 60000, 0, rdata.bytesize].pack('nnNn') + rdata).b - end - - private def generic_question(type) - (header(1, 0) + "\x07example\x03com\x00".b + [type, 60000].pack('nn')).b - end - - private def decode_generic(wire) - Resolv::DNS::Message.decode(wire).answer.first[2] - end - def test_srv_no_compress # Domain name in SRV RDATA should not be compressed issue29 = 'https://github.com/ruby/resolv/issues/29' From 20ca4e1f647107df1a5ae75b514f20b587091405 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Jun 2026 01:00:28 +0900 Subject: [PATCH 16/17] [Bug #22322] Take transcoder names from declared transcoders Fix use-after-free of dangling pointers. --- test/ruby/test_econv.rb | 14 ++++++++++++++ transcode.c | 2 ++ 2 files changed, 16 insertions(+) diff --git a/test/ruby/test_econv.rb b/test/ruby/test_econv.rb index 1d0641e91847a0..278e39e4511935 100644 --- a/test/ruby/test_econv.rb +++ b/test/ruby/test_econv.rb @@ -856,6 +856,20 @@ def test_iso2022jp_invalid_replace "\222\xA1x".encode("iso-2022-jp", "stateless-iso-2022-jp", :invalid => :replace)) end + def test_inspect_decorator_source_name + decorator_names = %w[ + amp_escape xml_text_escape xml_attr_content_escape xml_attr_quote + universal_newline crlf_newline cr_newline lf_newline + ] + all_assertions_foreach(nil, *decorator_names) do |decorator| + ec = Encoding::Converter.new('', decorator) + inspect = ec.inspect + 10_000.times {"A"*100} + GC.start(full_mark: true, immediate_sweep: true) + assert_equal inspect, ec.inspect + end + end + def test_convpath eucjp = Encoding::EUC_JP utf8 = Encoding::UTF_8 diff --git a/transcode.c b/transcode.c index de363c9dcd5789..35e5b43dce9a15 100644 --- a/transcode.c +++ b/transcode.c @@ -1045,6 +1045,8 @@ rb_econv_open0(const char *sname, const char *dname, int ecflags) SIZED_FREE_N(entries, num_trans); return NULL; } + sname = entries[0]->sname; + dname = entries[num_trans-1]->dname; } ec = rb_econv_open_by_transcoder_entries(num_trans, entries); From d38952c45e27becbe160f832aea823546899ad2e Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 16 Sep 2026 17:18:53 +0900 Subject: [PATCH 17/17] 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 --- proc.c | 3 +++ test/ruby/test_method.rb | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/proc.c b/proc.c index 6206f2c5da668e..ddb834e4645bf6 100644 --- a/proc.c +++ b/proc.c @@ -4090,6 +4090,9 @@ method_inspect(VALUE method) } else { mklass = data->klass; + if (RB_TYPE_P(mklass, T_ICLASS)) { + mklass = RBASIC_CLASS(mklass); + } if (RCLASS_SINGLETON_P(mklass)) { VALUE v = RCLASS_ATTACHED_OBJECT(mklass); if (!(RB_TYPE_P(v, T_CLASS) || RB_TYPE_P(v, T_MODULE))) { diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index 6285c9e1a410bf..b9e4c320f45e45 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -558,6 +558,12 @@ def o.foo; end; line_no = __LINE__ c5.extend(m) c6 = Class.new(c5) assert_equal("#(#{m.inspect})#prep(prepend)() #{__FILE__}:#{line_no}>", c6.method(:prep).inspect, bug17428) + + mod = Module.new { def foo; end }; line_no = __LINE__ + cls = Class.new { include mod } + o = cls.new + assert_equal("#", + o.method(:foo).unbind.bind(o).inspect, "[ruby-core:126737] [Bug #22321]") end def test_callee_top_level