From 1f0ca55750413603057fabef39550feb9e7fc3c8 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 16:59:26 -0800 Subject: [PATCH 01/12] make-snapshot: Update the tag format for Ruby 4.0+ (#15514) --- tool/make-snapshot | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tool/make-snapshot b/tool/make-snapshot index 041b1a0f2aba6d..4af6a855ebb793 100755 --- a/tool/make-snapshot +++ b/tool/make-snapshot @@ -272,10 +272,18 @@ def package(vcs, rev, destdir, tmp = nil) when /\A(\d+)\.(\d+)\.(\d+)-(preview|rc)(\d+)/ prerelease = true tag = "#{$4}#{$5}" - url = vcs.tag("v#{$1}_#{$2}_#{$3}_#{$4}#{$5}") + if Integer($1) >= 4 + url = vcs.tag("v#{rev}") + else + url = vcs.tag("v#{$1}_#{$2}_#{$3}_#{$4}#{$5}") + end when /\A(\d+)\.(\d+)\.(\d+)\z/ tag = "" - url = vcs.tag("v#{$1}_#{$2}_#{$3}") + if Integer($1) >= 4 + url = vcs.tag("v#{rev}") + else + url = vcs.tag("v#{$1}_#{$2}_#{$3}") + end when /\A(\d+)\.(\d+)\z/ url = vcs.branch("ruby_#{rev.tr('.', '_')}") else From 3a76625915e57eb328d23ae5dd621d8bf45b30e0 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Thu, 11 Dec 2025 18:28:11 -0700 Subject: [PATCH 02/12] ZJIT: Don't specialize calls with kwsplat (#15513) --- test/ruby/test_zjit.rb | 9 +++++++++ zjit/src/hir.rs | 1 + zjit/src/hir/opt_tests.rs | 27 +++++++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 49b3616425ecd3..232c46079f5b38 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -626,6 +626,15 @@ def entry = test(e: :e, d: :d, c: :c, a: :a, b: :b) }, call_threshold: 2 end + def test_send_kwsplat + assert_compiles '3', %q{ + def test(a:) = a + def entry = test(**{a: 3}) + entry + entry + }, call_threshold: 2 + end + def test_send_kwrest assert_compiles '{a: 3}', %q{ def test(**kwargs) = kwargs diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 4992f177991d47..1cdc5e003a7cf2 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -5229,6 +5229,7 @@ fn unspecializable_c_call_type(flags: u32) -> bool { /// If a given call uses overly complex arguments, then we won't specialize. fn unspecializable_call_type(flags: u32) -> bool { ((flags & VM_CALL_ARGS_SPLAT) != 0) || + ((flags & VM_CALL_KW_SPLAT) != 0) || ((flags & VM_CALL_ARGS_BLOCKARG) != 0) } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index a602121a0cc51d..3946c9d03c0714 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2993,6 +2993,33 @@ mod hir_opt_tests { "); } + #[test] + fn dont_specialize_call_to_iseq_with_call_kwsplat() { + eval(" + def foo(a:) = a + def test = foo(**{a: 1}) + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:HashExact = HashDup v11 + IncrCounter complex_arg_pass_caller_kw_splat + v14:BasicObject = SendWithoutBlock v6, :foo, v12 # SendFallbackReason: Complex argument passing + CheckInterrupts + Return v14 + "); + } + #[test] fn dont_specialize_call_to_iseq_with_param_kwrest() { eval(" From faac344d1698b3f6b7e2bd8afbca1b7fbc46b9df Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 17:45:07 -0800 Subject: [PATCH 03/12] make-snapshot: Fix Psych::DisallowedClass with newer psych ``` $ tool/format-release ../www.ruby-lang.org 4.0.0-preview2 . /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/class_loader.rb:99:in 'Psych::ClassLoader::Restricted#find': Tried to load unspecified class: Date (Psych::DisallowedClass) from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/class_loader.rb:28:in 'Psych::ClassLoader#load' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/class_loader.rb:40:in 'Psych::ClassLoader#date' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/scalar_scanner.rb:65:in 'Psych::ScalarScanner#tokenize' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:65:in 'Psych::Visitors::ToRuby#deserialize' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:129:in 'Psych::Visitors::ToRuby#visit_Psych_Nodes_Scalar' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:30:in 'Psych::Visitors::Visitor#visit' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:6:in 'Psych::Visitors::Visitor#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:35:in 'Psych::Visitors::ToRuby#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:346:in 'block in Psych::Visitors::ToRuby#revive_hash' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:344:in 'Array#each' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:344:in 'Enumerable#each_slice' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:344:in 'Psych::Visitors::ToRuby#revive_hash' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:168:in 'Psych::Visitors::ToRuby#visit_Psych_Nodes_Mapping' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:30:in 'Psych::Visitors::Visitor#visit' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:6:in 'Psych::Visitors::Visitor#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:35:in 'Psych::Visitors::ToRuby#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:339:in 'block in Psych::Visitors::ToRuby#register_empty' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:339:in 'Array#each' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:339:in 'Psych::Visitors::ToRuby#register_empty' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:147:in 'Psych::Visitors::ToRuby#visit_Psych_Nodes_Sequence' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:30:in 'Psych::Visitors::Visitor#visit' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:6:in 'Psych::Visitors::Visitor#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:35:in 'Psych::Visitors::ToRuby#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:319:in 'Psych::Visitors::ToRuby#visit_Psych_Nodes_Document' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:30:in 'Psych::Visitors::Visitor#visit' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/visitor.rb:6:in 'Psych::Visitors::Visitor#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych/visitors/to_ruby.rb:35:in 'Psych::Visitors::ToRuby#accept' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych.rb:336:in 'Psych.safe_load' from /opt/rubies/3.4.6/lib/ruby/3.4.0/psych.rb:371:in 'Psych.load' from tool/format-release:80:in 'Tarball.parse' from tool/format-release:269:in 'Object#main' from tool/format-release:272:in '
' ``` --- tool/format-release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/format-release b/tool/format-release index b38263f9f4fb05..0505df700b0946 100755 --- a/tool/format-release +++ b/tool/format-release @@ -77,7 +77,7 @@ eom end uri = "https://cache.ruby-lang.org/pub/tmp/ruby-info-#{version}-draft.yml" - info = YAML.load(URI(uri).read) + info = YAML.unsafe_load(URI(uri).read) if info.size != 1 raise "unexpected info.yml '#{uri}'" end From ec4c46709b74ae11a1d90ad3b2e8c9c79ee4afee Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 17:43:46 -0800 Subject: [PATCH 04/12] tool/format-release: Carve out the version format logic to share it with tool/releng/update-www-meta.rb and another place I'm going to modify next. --- tool/format-release | 39 ++++++---------------------------- tool/releng/update-www-meta.rb | 39 ++++++---------------------------- tool/ruby-version.rb | 37 ++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 66 deletions(-) create mode 100644 tool/ruby-version.rb diff --git a/tool/format-release b/tool/format-release index 0505df700b0946..67c8760e58547b 100755 --- a/tool/format-release +++ b/tool/format-release @@ -9,6 +9,7 @@ end require "open-uri" require "yaml" +require_relative "./ruby-version" Diffy::Diff.default_options.merge!( include_diff_info: true, @@ -54,27 +55,7 @@ eom unless /\A(\d+)\.(\d+)\.(\d+)(?:-(?:preview|rc)\d+)?\z/ =~ version raise "unexpected version string '#{version}'" end - x = $1.to_i - y = $2.to_i - z = $3.to_i - # previous tag for git diff --shortstat - # It's only for x.y.0 release - if z != 0 - prev_tag = nil - elsif y != 0 - prev_ver = "#{x}.#{y-1}.0" - prev_tag = version_tag(prev_ver) - else # y == 0 && z == 0 - case x - when 3 - prev_ver = "2.7.0" - when 4 - prev_ver = "3.4.0" - else - raise "it doesn't know what is the previous version of '#{version}'" - end - prev_tag = version_tag(prev_ver) - end + teeny = Integer($3) uri = "https://cache.ruby-lang.org/pub/tmp/ruby-info-#{version}-draft.yml" info = YAML.unsafe_load(URI(uri).read) @@ -92,9 +73,10 @@ eom tarballs << tarball end - if prev_tag + if teeny == 0 # show diff shortstat - tag = version_tag(version) + tag = RubyVersion.tag(version) + prev_tag = RubyVersion.tag(RubyVersion.previous(version)) stat = `git -C #{rubydir} diff -l0 --shortstat #{prev_tag}..#{tag}` files_changed, insertions, deletions = stat.scan(/\d+/) end @@ -188,7 +170,7 @@ eom if /\.0(?:-\w+)?\z/ =~ ver # preview, rc, or first release entry <<= <= 4 - "v#{version}" - else - "v#{version.tr('.-', '_')}" - end - end end def main diff --git a/tool/releng/update-www-meta.rb b/tool/releng/update-www-meta.rb index 100f0bee181806..0dd5b2563122e7 100755 --- a/tool/releng/update-www-meta.rb +++ b/tool/releng/update-www-meta.rb @@ -1,6 +1,7 @@ #!/usr/bin/env ruby require "open-uri" require "yaml" +require_relative "../ruby-version" class Tarball attr_reader :version, :size, :sha1, :sha256, :sha512 @@ -41,27 +42,7 @@ def self.parse(wwwdir, version) unless /\A(\d+)\.(\d+)\.(\d+)(?:-(?:preview|rc)\d+)?\z/ =~ version raise "unexpected version string '#{version}'" end - x = $1.to_i - y = $2.to_i - z = $3.to_i - # previous tag for git diff --shortstat - # It's only for x.y.0 release - if z != 0 - prev_tag = nil - elsif y != 0 - prev_ver = "#{x}.#{y-1}.0" - prev_tag = version_tag(prev_ver) - else # y == 0 && z == 0 - case x - when 3 - prev_ver = "2.7.0" - when 4 - prev_ver = "3.4.0" - else - raise "it doesn't know what is the previous version of '#{version}'" - end - prev_tag = version_tag(prev_ver) - end + teeny = Integer($3) uri = "https://cache.ruby-lang.org/pub/tmp/ruby-info-#{version}-draft.yml" info = YAML.load(URI(uri).read) @@ -79,9 +60,10 @@ def self.parse(wwwdir, version) tarballs << tarball end - if prev_tag + if teeny == 0 # show diff shortstat - tag = version_tag(version) + tag = RubyVersion.tag(version) + prev_tag = RubyVersion.tag(RubyVersion.previous(version)) rubydir = File.expand_path(File.join(__FILE__, '../../../')) puts %`git -C #{rubydir} diff --shortstat #{prev_tag}..#{tag}` stat = `git -C #{rubydir} diff --shortstat #{prev_tag}..#{tag}` @@ -160,7 +142,7 @@ def self.update_releases_yml(ver, xy, ary, wwwdir, files_changed, insertions, de date = Time.now.utc # use utc to use previous day in midnight entry = <= 4 - "v#{version}" - else - "v#{version.tr('.-', '_')}" - end - end end # Confirm current directory is www.ruby-lang.org's working directory diff --git a/tool/ruby-version.rb b/tool/ruby-version.rb new file mode 100644 index 00000000000000..aaaa345e75b356 --- /dev/null +++ b/tool/ruby-version.rb @@ -0,0 +1,37 @@ +module RubyVersion + def self.tag(version) + major_version = Integer(version.split('.', 2)[0]) + if major_version >= 4 + "v#{version}" + else + "v#{version.tr('.-', '_')}" + end + end + + # Return the previous version to be used for release diff links. + # For a ".0" version, it returns the previous ".0" version. + # For a non-".0" version, it returns the previous teeny version. + def self.previous(version) + unless /\A(\d+)\.(\d+)\.(\d+)(?:-(?:preview|rc)\d+)?\z/ =~ version + raise "unexpected version string '#{version}'" + end + major = Integer($1) + minor = Integer($2) + teeny = Integer($3) + + if teeny != 0 + "#{major}.#{minor}.#{teeny-1}" + elsif minor != 0 # && teeny == 0 + "#{major}.#{minor-1}.#{teeny}" + else # minor == 0 && teeny == 0 + case major + when 3 + "2.7.0" + when 4 + "3.4.0" + else + raise "it doesn't know what is the previous version of '#{version}'" + end + end + end +end From 8fba4b0f6008b68845be89861ddb73190d53511e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 11 Dec 2025 17:54:48 -0800 Subject: [PATCH 05/12] tool/format-release: Fix a wrong method reference --- tool/format-release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/format-release b/tool/format-release index 67c8760e58547b..8bb61542433fb2 100755 --- a/tool/format-release +++ b/tool/format-release @@ -170,7 +170,7 @@ eom if /\.0(?:-\w+)?\z/ =~ ver # preview, rc, or first release entry <<= < Date: Thu, 11 Dec 2025 17:57:37 -0800 Subject: [PATCH 06/12] release.yml: Fix tag conversion for Ruby 4.0 and PREVIOUS_RELEASE_TAG for any .0 releases --- .github/workflows/release.yml | 4 ++-- tool/ruby-version.rb | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) mode change 100644 => 100755 tool/ruby-version.rb diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03227bcd7234fd..3caeee9a3b8796 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,9 +49,9 @@ jobs: - name: Create a release on GitHub run: | - RELEASE_TAG=$(echo v${{ env.RUBY_VERSION }} | sed 's/\./_/g') + RELEASE_TAG=$(ruby tool/ruby-version.rb tag "${{ env.RUBY_VERSION }}") echo $RELEASE_TAG - PREVIOUS_RELEASE_TAG=$(echo $RELEASE_TAG | awk 'BEGIN {FS="_"; OFS="_"}{ $NF=$NF-1; print }') + PREVIOUS_RELEASE_TAG=$(ruby tool/ruby-version.rb previous-tag "${{ env.RUBY_VERSION }}") echo $PREVIOUS_RELEASE_TAG tool/gen-github-release.rb $PREVIOUS_RELEASE_TAG $RELEASE_TAG --no-dry-run env: diff --git a/tool/ruby-version.rb b/tool/ruby-version.rb old mode 100644 new mode 100755 index aaaa345e75b356..3bbec576e13ad4 --- a/tool/ruby-version.rb +++ b/tool/ruby-version.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + module RubyVersion def self.tag(version) major_version = Integer(version.split('.', 2)[0]) @@ -35,3 +37,16 @@ def self.previous(version) end end end + +if __FILE__ == $0 + case ARGV[0] + when "tag" + print RubyVersion.tag(ARGV[1]) + when "previous" + print RubyVersion.previous(ARGV[1]) + when "previous-tag" + print RubyVersion.tag(RubyVersion.previous(ARGV[1])) + else + "#{$0}: unexpected command #{ARGV[0].inspect}" + end +end From 04494d9e4064a57accc8309da9b35754a3d24973 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Fri, 12 Dec 2025 12:00:35 +0900 Subject: [PATCH 07/12] `Binding#local_variable_defined?` must not handle numbered parameters [Bug #21776] --- proc.c | 3 +++ test/ruby/test_proc.rb | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/proc.c b/proc.c index 2df8fbee5e23a9..887fcae384b5fb 100644 --- a/proc.c +++ b/proc.c @@ -640,6 +640,9 @@ bind_local_variable_defined_p(VALUE bindval, VALUE sym) const rb_env_t *env; if (!lid) return Qfalse; + if (rb_numparam_id_p(lid)) { + return Qfalse; + } GetBindingPtr(bindval, bind); env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block)); diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index 2cd97ca32464ab..d6bd8e724ee046 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1659,29 +1659,35 @@ def test_numparam_is_not_local_variables assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_equal(false, binding.local_variable_defined?(:_9)) "bar".tap do assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_equal(false, binding.local_variable_defined?(:_9)) end assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_equal(false, binding.local_variable_defined?(:_9)) end "foo".tap do assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_equal(false, binding.local_variable_defined?(:_9)) "bar".tap do _9 and flunk assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_equal(false, binding.local_variable_defined?(:_9)) end assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_equal(false, binding.local_variable_defined?(:_9)) end end @@ -1690,31 +1696,39 @@ def test_it_is_not_local_variable it assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) "bar".tap do assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) end assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) "bar".tap do it assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) end assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) end "foo".tap do assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) "bar".tap do it assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) end assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) end end From 50e5c542cc0541fb38e52766d88d87bd8a96b072 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Dec 2025 12:39:59 +0900 Subject: [PATCH 08/12] Win32: Remove the workaround for console reading bug It has been fixed at Windows 8, and we already have dropped the support Windows 8 and olders. --- win32/win32.c | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/win32/win32.c b/win32/win32.c index 66ce195092f5e5..3d7e54932402da 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -7223,9 +7223,6 @@ rb_w32_read_internal(int fd, void *buf, size_t size, rb_off_t *offset) size_t len; size_t ret; OVERLAPPED ol; - BOOL isconsole; - BOOL islineinput = FALSE; - int start = 0; if (is_socket(sock)) return rb_w32_recv(fd, buf, size, 0); @@ -7248,25 +7245,8 @@ rb_w32_read_internal(int fd, void *buf, size_t size, rb_off_t *offset) } ret = 0; - isconsole = is_console(_osfhnd(fd)) && (osver.dwMajorVersion < 6 || (osver.dwMajorVersion == 6 && osver.dwMinorVersion < 2)); - if (isconsole) { - DWORD mode; - GetConsoleMode((HANDLE)_osfhnd(fd),&mode); - islineinput = (mode & ENABLE_LINE_INPUT) != 0; - } retry: - /* get rid of console reading bug */ - if (isconsole) { - constat_reset((HANDLE)_osfhnd(fd)); - if (start) - len = 1; - else { - len = 0; - start = 1; - } - } - else - len = size; + len = size; size -= len; if (setup_overlapped(&ol, fd, FALSE, offset)) { @@ -7337,8 +7317,7 @@ rb_w32_read_internal(int fd, void *buf, size_t size, rb_off_t *offset) ret += read; if (read >= len) { buf = (char *)buf + read; - if (err != ERROR_OPERATION_ABORTED && - !(isconsole && len == 1 && (!islineinput || *((char *)buf - 1) == '\n')) && size > 0) + if (err != ERROR_OPERATION_ABORTED && size > 0) goto retry; } if (read == 0) From f939f0433ab53bc1a8d567e0b52a09a95ce78bfb Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Dec 2025 12:54:24 +0900 Subject: [PATCH 09/12] Win32: Deprecate Windows version info API `dwMajorVersion` alone has no meaning since Windows 7. Use API in VersionHelper.h instead. --- include/ruby/win32.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/include/ruby/win32.h b/include/ruby/win32.h index 8d9f9ddd80be6b..4255661dd49f2f 100644 --- a/include/ruby/win32.h +++ b/include/ruby/win32.h @@ -262,7 +262,6 @@ struct ifaddrs { #endif extern void rb_w32_sysinit(int *, char ***); -extern DWORD rb_w32_osid(void); extern int flock(int fd, int oper); extern int rb_w32_io_cancelable_p(int); extern int rb_w32_is_socket(int); @@ -306,7 +305,11 @@ extern void rb_w32_free_environ(char **); extern int rb_w32_map_errno(DWORD); extern const char *WSAAPI rb_w32_inet_ntop(int,const void *,char *,size_t); extern int WSAAPI rb_w32_inet_pton(int,const char *,void *); -extern DWORD rb_w32_osver(void); + +RBIMPL_ATTR_DEPRECATED(("as Windows 9x is not supported already")) +extern DWORD rb_w32_osid(void); +RBIMPL_ATTR_DEPRECATED(("by Windows Version Helper APIs")) +extern DWORD rb_w32_osver(void); extern int rb_w32_uchown(const char *, int, int); extern int rb_w32_ulink(const char *, const char *); From 5541c0d896d220923e795aa4f87ceb6237d53c4b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Dec 2025 13:04:09 +0900 Subject: [PATCH 10/12] Win32: Make `rb_w32_osid` return Windows NT always Since support for Windows 9x was dropped over a decade ago. --- include/ruby/win32.h | 2 +- win32/win32.c | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/include/ruby/win32.h b/include/ruby/win32.h index 4255661dd49f2f..ae11a61481e74e 100644 --- a/include/ruby/win32.h +++ b/include/ruby/win32.h @@ -307,7 +307,7 @@ extern const char *WSAAPI rb_w32_inet_ntop(int,const void *,char *,size_t); extern int WSAAPI rb_w32_inet_pton(int,const char *,void *); RBIMPL_ATTR_DEPRECATED(("as Windows 9x is not supported already")) -extern DWORD rb_w32_osid(void); +static inline DWORD rb_w32_osid(void) {return VER_PLATFORM_WIN32_NT;} RBIMPL_ATTR_DEPRECATED(("by Windows Version Helper APIs")) extern DWORD rb_w32_osver(void); diff --git a/win32/win32.c b/win32/win32.c index 3d7e54932402da..97dc7148085c55 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -314,15 +314,6 @@ get_version(void) GetVersionEx(&osver); } -#ifdef _M_IX86 -/* License: Artistic or GPL */ -DWORD -rb_w32_osid(void) -{ - return osver.dwPlatformId; -} -#endif - /* License: Artistic or GPL */ DWORD rb_w32_osver(void) From 1794cfe12fe61dedebadead542927f9fef4104eb Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Fri, 12 Dec 2025 14:44:09 +0900 Subject: [PATCH 11/12] Binding#local_variable_defined? raises a NameError for numbered params. [Bug #21776] --- proc.c | 3 ++- test/ruby/test_proc.rb | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/proc.c b/proc.c index 887fcae384b5fb..1a57757d846ceb 100644 --- a/proc.c +++ b/proc.c @@ -641,7 +641,8 @@ bind_local_variable_defined_p(VALUE bindval, VALUE sym) if (!lid) return Qfalse; if (rb_numparam_id_p(lid)) { - return Qfalse; + rb_name_err_raise("numbered parameter '%1$s' is not a local variable", + bindval, ID2SYM(lid)); } GetBindingPtr(bindval, bind); diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index d6bd8e724ee046..2eeb7a94ebb706 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1659,35 +1659,35 @@ def test_numparam_is_not_local_variables assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } - assert_equal(false, binding.local_variable_defined?(:_9)) + assert_raise(NameError) { binding.local_variable_defined?(:_9) } "bar".tap do assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } - assert_equal(false, binding.local_variable_defined?(:_9)) + assert_raise(NameError) { binding.local_variable_defined?(:_9) } end assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } - assert_equal(false, binding.local_variable_defined?(:_9)) + assert_raise(NameError) { binding.local_variable_defined?(:_9) } end "foo".tap do assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } - assert_equal(false, binding.local_variable_defined?(:_9)) + assert_raise(NameError) { binding.local_variable_defined?(:_9) } "bar".tap do _9 and flunk assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } - assert_equal(false, binding.local_variable_defined?(:_9)) + assert_raise(NameError) { binding.local_variable_defined?(:_9) } end assert_equal([], binding.local_variables) assert_raise(NameError) { binding.local_variable_get(:_9) } assert_raise(NameError) { binding.local_variable_set(:_9, 1) } - assert_equal(false, binding.local_variable_defined?(:_9)) + assert_raise(NameError) { binding.local_variable_defined?(:_9) } end end From 5ef4f88d5e0cea9a36702f8165a4d47a11c2a703 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Fri, 12 Dec 2025 15:01:27 +0900 Subject: [PATCH 12/12] use `ractor_sched_lock` instead of using `rb_native_mutex_lock` directly. --- thread_pthread_mn.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/thread_pthread_mn.c b/thread_pthread_mn.c index 2211d4d1067c39..ad4d0915e790b2 100644 --- a/thread_pthread_mn.c +++ b/thread_pthread_mn.c @@ -1078,12 +1078,12 @@ timer_thread_polling(rb_vm_t *vm) switch (r) { case 0: // timeout - rb_native_mutex_lock(&vm->ractor.sched.lock); + ractor_sched_lock(vm, NULL); { // (1-1) timeslice timer_thread_check_timeslice(vm); } - rb_native_mutex_unlock(&vm->ractor.sched.lock); + ractor_sched_unlock(vm, NULL); break; case -1: // error