From 38850f7091d91dbc667f1dd1a1a36e120476198c Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sat, 12 Sep 2026 22:47:20 +0200 Subject: [PATCH 01/11] Add Thread::Backtrace::Location#source_range specs for more assignments Cover constant &&=, constant path writes and operator assignments failing on the namespace, bare constant operator assignments failing in the operator or on the value, instance and class variable operator assignments, index and attribute ||= and &&=, safe navigation attribute operator assignments, multiple assignment targets and values, and attribute targets in for loops and rescue clauses. Four of these still differ with parse.y and are skipped there: class variable reads in operator assignments, parenthesized nested multiple assignment and block parameter targets, and rescue targets. Co-Authored-By: Claude Fable 5.1 --- .../backtrace/location/source_range_spec.rb | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/spec/ruby/core/thread/backtrace/location/source_range_spec.rb b/spec/ruby/core/thread/backtrace/location/source_range_spec.rb index 24683602406551..696a920a42485a 100644 --- a/spec/ruby/core/thread/backtrace/location/source_range_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/source_range_spec.rb @@ -167,6 +167,183 @@ def value.foo=(new_value) $SourceRangeNotDefined += 1$ RUBY + # The constant is defined in a singleton class body so it does not leak + "top-level constant operator assignments failing in the operator" => [<<-RUBY, :ConstantOperatorWriteNode], + class << Object.new + Nil = nil + $Nil += 1$ + end + RUBY + + "top-level constant operator assignments failing on the value" => [<<-RUBY, :ConstantOperatorWriteNode], + class << Object.new + One = 1 + $One += nil$ + end + RUBY + + "top-level constant &&= assignments failing while reading" => [<<-RUBY, :ConstantAndWriteNode], + $SourceRangeNotDefined &&= 1$ + RUBY + + "top-level ::constant &&= assignments failing while reading" => [<<-RUBY, :ConstantPathAndWriteNode], + $::SourceRangeNotDefined &&= 1$ + RUBY + + "constant &&= assignments failing while reading" => [<<-RUBY, :ConstantPathAndWriteNode], + namespace = Module.new + $namespace::NotDefined &&= 1$ + RUBY + + "constant operator assignments failing on the namespace" => [<<-RUBY, :ConstantPathOperatorWriteNode], + namespace = nil + $namespace::NotDefined += 1$ + RUBY + + "constant ||= assignments failing on the namespace" => [<<-RUBY, :ConstantPathOrWriteNode], + namespace = nil + $namespace::NotDefined ||= 1$ + RUBY + + "constant &&= assignments failing on the namespace" => [<<-RUBY, :ConstantPathAndWriteNode], + namespace = nil + $namespace::NotDefined &&= 1$ + RUBY + + "constant writes failing on the namespace" => [<<-RUBY, :ConstantPathWriteNode], + namespace = nil + $namespace::NotDefined = 1$ + RUBY + + "constant writes failing while reading the namespace" => [<<-RUBY, :ConstantReadNode], + $SourceRangeNotDefined$::Nested = 1 + RUBY + + "instance variable operator assignments" => [<<-RUBY, :InstanceVariableOperatorWriteNode], + $@source_range_value += 1$ + RUBY + + "class variable operator assignments failing while reading" => [<<-RUBY, :ClassVariableOperatorWriteNode], + class SourceRangeClassVariableSpecs + $@@not_defined += 1$ + end + RUBY + + "class variable operator assignments failing in the operator" => [<<-RUBY, :ClassVariableOperatorWriteNode], + class SourceRangeClassVariableSpecs + @@nil = nil + $@@nil += 1$ + end + RUBY + + "index ||= assignments failing while reading" => [<<-RUBY, :IndexOrWriteNode], + value = nil + $value[0] ||= 42$ + RUBY + + "index ||= assignments failing while writing" => [<<-RUBY, :IndexOrWriteNode], + value = Object.new + def value.[](index) = nil + $value[0] ||= 42$ + RUBY + + "index &&= assignments failing while reading" => [<<-RUBY, :IndexAndWriteNode], + value = nil + $value[0] &&= 42$ + RUBY + + "index &&= assignments failing while writing" => [<<-RUBY, :IndexAndWriteNode], + value = Object.new + def value.[](index) = 1 + $value[0] &&= 42$ + RUBY + + "attribute ||= assignments failing while reading" => [<<-RUBY, :CallOrWriteNode], + value = nil + $value.foo ||= 42$ + RUBY + + "attribute ||= assignments failing while writing" => [<<-RUBY, :CallOrWriteNode], + value = Object.new + def value.foo = nil + $value.foo ||= 42$ + RUBY + + "attribute &&= assignments failing while reading" => [<<-RUBY, :CallAndWriteNode], + value = nil + $value.foo &&= 42$ + RUBY + + "attribute &&= assignments failing while writing" => [<<-RUBY, :CallAndWriteNode], + value = Object.new + def value.foo = 1 + $value.foo &&= 42$ + RUBY + + "safe navigation attribute operator assignments" => [<<-RUBY, :CallOperatorWriteNode], + value = Object.new + def value.foo = nil + $value&.foo += 1$ + RUBY + + "multiple assignments with an attribute target" => [<<-RUBY, :CallTargetNode], + value = nil + $value.foo$, other = 1, 2 + RUBY + + "multiple assignments with a splat attribute target" => [<<-RUBY, :CallTargetNode], + value = nil + *$value.foo$ = 1, 2 + RUBY + + "multiple assignments with an index target" => [<<-RUBY, :IndexTargetNode], + value = nil + $value[0]$, other = 1, 2 + RUBY + + "multiple assignments with a constant path target" => [<<-RUBY, :ConstantPathTargetNode], + namespace = nil + $namespace::NotDefined$, other = 1, 2 + RUBY + + "multiple assignments converting the value" => [<<-RUBY, :MultiWriteNode, 1], + value = Object.new + def value.to_ary = raise(TypeError) + $first, second = value$ + RUBY + + "multiple assignments splatting the value" => [<<-RUBY, :ArrayNode, 1], + value = Object.new + def value.to_a = raise(TypeError) + first, second = $*value$ + RUBY + + "nested multiple assignments" => [<<-RUBY, :MultiTargetNode, 1], + value = Object.new + def value.to_ary = raise(TypeError) + $(first, second)$, third = value, 1 + RUBY + + "destructuring block parameters" => [<<-RUBY, :MultiTargetNode, 1], + value = Object.new + def value.to_ary = raise(TypeError) + [value].each { |$(first, second)$| } + RUBY + + "for loops with an attribute target" => [<<-RUBY, :CallTargetNode], + value = nil + for $value.foo$ in [1] + end + RUBY + + "rescue with an attribute target" => [<<-RUBY, :CallTargetNode], + value = nil + begin + raise "error" + rescue => $value.foo$ + end + RUBY + "explicit #raise" => [<<-RUBY, :CallNode], $raise NameError$ RUBY @@ -376,6 +553,15 @@ def value.to_s RUBY }.each_pair do |description, (source, prism_class, frame)| it "returns the precise range for #{description}" do + # Currently fails with parse.y, needs to be fixed + parse_y_failures = [ + "class variable operator assignments failing while reading", + "nested multiple assignments", + "destructuring block parameters", + "rescue with an attribute target", + ] + skip "parse.y" if parse_y_failures.include?(description) && !syntax_tree_returns_prism_node + capture_backtrace_location_source_range(source, prism_class, frame: frame || 0) end end From b723da3bb27de4f66c8716bdc488e2b43650d7d3 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 12 Sep 2026 13:24:03 +0900 Subject: [PATCH 02/11] Run the IO.copy_stream socket tests on Windows rb_w32_pread() has existed for a while, so the reason these four gave was stale. Three of them pass as they are; the fourth needs fork(), which is what it was really waiting for. Co-Authored-By: Claude Opus 5 --- test/ruby/test_io.rb | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index cd3245761546fd..0679fe3587b99f 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -1076,10 +1076,6 @@ def test_copy_stream_socket3 end if defined? UNIXSocket def test_copy_stream_socket4 - if RUBY_PLATFORM =~ /mingw|mswin/ - omit "pread(2) is not implemented." - end - with_bigsrc {|bigsrc, bigcontent| File.open(bigsrc) {|f| assert_equal(0, f.pos) @@ -1099,10 +1095,6 @@ def test_copy_stream_socket4 end def test_copy_stream_socket5 - if RUBY_PLATFORM =~ /mingw|mswin/ - omit "pread(2) is not implemented." - end - with_bigsrc {|bigsrc, bigcontent| File.open(bigsrc) {|f| assert_equal(bigcontent[0,100], f.read(100)) @@ -1123,10 +1115,6 @@ def test_copy_stream_socket5 end def test_copy_stream_socket6 - if RUBY_PLATFORM =~ /mingw|mswin/ - omit "pread(2) is not implemented." - end - mkcdtmpdir { megacontent = "abc" * 1234567 File.open("megasrc", "w") {|f| f << megacontent } @@ -1150,9 +1138,7 @@ def test_copy_stream_socket6 end def test_copy_stream_socket7 - if RUBY_PLATFORM =~ /mingw|mswin/ - omit "pread(2) is not implemented." - end + omit "fork is not supported" unless Process.respond_to?(:fork) GC.start mkcdtmpdir { From d4ffc1256639cc3867578dc2fe2d1043a60eab23 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 12 Sep 2026 13:24:10 +0900 Subject: [PATCH 03/11] Run test_handle_interrupt_masks_sigint on Windows It signals its own process, which kill() turns into a plain raise(), so nothing here depends on the console control events that the other two signal tests in this file need. Co-Authored-By: Claude Opus 5 --- test/ruby/test_thread.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index e316c410cce828..b9f97ac1d54ca3 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -848,10 +848,6 @@ def test_handle_interrupt_blocking end def test_handle_interrupt_masks_sigint - if /mswin|mingw/ =~ RUBY_PLATFORM - omit "SIGINT handling differs on Windows" - end - assert_in_out_err([], <<-INPUT, %w(outer false), []) waiting = Thread::Queue.new release = Thread::Queue.new From 8e9940026a53aaf0ccd4b84c8a596e98fc272213 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 12 Sep 2026 13:32:40 +0900 Subject: [PATCH 04/11] Run test_io_priority_wait_on_mn_thread on Windows Windows builds with USE_MN_THREADS 0, so this no longer covers the M:N path it is named for, but IO#wait(IO::PRIORITY) waking on an OOB byte is worth asserting there too, and it does. Co-Authored-By: Claude Opus 5 --- test/ruby/test_ractor.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index 93af32a3a1741d..44273d19183698 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -899,7 +899,6 @@ def test_move_array_sharing_its_embedded_elements end def test_io_priority_wait_on_mn_thread - omit 'POLLPRI/MSG_OOB semantics differ on windows' if RUBY_PLATFORM =~ /mswin|mingw/ # A timeout-less IO#wait(IO::PRIORITY) on an M:N thread must take the # blocking path: the M:N scheduler has no event for POLLPRI and used to # register nothing yet park the thread forever. From f34901e27f90127c6a1407c54e9282c5b5c9f5ca Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 12 Sep 2026 13:34:15 +0900 Subject: [PATCH 05/11] Run test_loading_extension_libs_in_main_box_2 on Windows What it hit was not a timeout but a shutdown crash in require "openssl" under RUBY_BOX=1, and that is gone. Give it the same 60 second timeout box_1 needed, since the 10 second default is what parallel CI runs out of. Co-Authored-By: Claude Opus 5 --- test/ruby/test_box.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 4fe6ee3696bda5..5d46906021f55f 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -1306,8 +1306,7 @@ def test_loading_extension_libs_in_main_box_1 end def test_loading_extension_libs_in_main_box_2 - pend if /mswin|mingw/ =~ RUBY_PLATFORM # timeout on windows environments - assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true, timeout: 60) begin; require "zlib" require "open3" From 934c31a6b35e9b9a8ca65a1282996e56580a8071 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 12 Sep 2026 13:39:31 +0900 Subject: [PATCH 06/11] Say what actually keeps test_epipe_on_read off Windows nonblock= works there; what does not happen is the write failing. The peer being closed leaves the next write on an AF_UNIX socket succeeding instead of raising EPIPE. Co-Authored-By: Claude Opus 5 --- test/fiber/test_io.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fiber/test_io.rb b/test/fiber/test_io.rb index eea06f97c829e7..06e22211246a93 100644 --- a/test/fiber/test_io.rb +++ b/test/fiber/test_io.rb @@ -67,7 +67,7 @@ def test_heavy_read def test_epipe_on_read omit unless defined?(UNIXSocket) - omit "nonblock=true isn't properly supported on Windows" if RUBY_PLATFORM =~ /mswin|mingw/ + omit "a closed peer does not make the next write fail on Windows" if RUBY_PLATFORM =~ /mswin|mingw/ i, o = UNIXSocket.pair From ab84f0a3aa6e1db57fd34aef50df2c118e560ab2 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 12 Sep 2026 13:52:34 +0900 Subject: [PATCH 07/11] Run test_machine_stack_size on Windows RUBY_THREAD_MACHINE_STACK_SIZE does reach CreateThread there, and the recursion depth it buys moves with it. Co-Authored-By: Claude Opus 5 --- test/ruby/test_thread.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index b9f97ac1d54ca3..f8806ec3083282 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -1232,7 +1232,7 @@ def test_machine_stack_size assert_operator(size_default, :>=, size_0, "0 size") size_large = invoke_rec script, vm_stack_size, 1024 * 1024 * 10 assert_operator(size_default, :<=, size_large, "large size") - end unless /mswin|mingw/ =~ RUBY_PLATFORM + end def test_blocking_mutex_unlocked_on_fork bug8433 = '[ruby-core:55102] [Bug #8433]' From 1a64fc0205dbf2c7a64c052639f8ecd0756c77cf Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 12 Sep 2026 14:05:20 +0900 Subject: [PATCH 08/11] Run the AF_UNIX specs on Windows AF_UNIX/SOCK_STREAM has been there since Windows 10, and Ruby has built UNIXSocket, UNIXServer and Socket.pair on it for a while now. Nothing in these four files needs anything else. Co-Authored-By: Claude Opus 5 --- .../ruby/library/socket/addrinfo/unix_spec.rb | 16 +- spec/ruby/library/socket/socket/pair_spec.rb | 198 +++++++++--------- .../ruby/library/socket/socket/socket_spec.rb | 18 +- spec/ruby/security/cve_2018_8779_spec.rb | 38 ++-- 4 files changed, 130 insertions(+), 140 deletions(-) diff --git a/spec/ruby/library/socket/addrinfo/unix_spec.rb b/spec/ruby/library/socket/addrinfo/unix_spec.rb index da65e13efb60bf..33a8822be56aca 100644 --- a/spec/ruby/library/socket/addrinfo/unix_spec.rb +++ b/spec/ruby/library/socket/addrinfo/unix_spec.rb @@ -55,15 +55,13 @@ end end - platform_is_not :windows do - describe "for a unix socket" do - before :each do - @addrinfo = Addrinfo.unix("/tmp/sock") - end - - it "returns true" do - @addrinfo.unix?.should == true - end + describe "for a unix socket" do + before :each do + @addrinfo = Addrinfo.unix("/tmp/sock") + end + + it "returns true" do + @addrinfo.unix?.should == true end end end diff --git a/spec/ruby/library/socket/socket/pair_spec.rb b/spec/ruby/library/socket/socket/pair_spec.rb index 91317a8d07de32..6bd34f279c0c2a 100644 --- a/spec/ruby/library/socket/socket/pair_spec.rb +++ b/spec/ruby/library/socket/socket/pair_spec.rb @@ -2,140 +2,138 @@ require_relative '../fixtures/classes' describe "Socket.pair" do - platform_is_not :windows do - it "ensures the returned sockets are connected" do - s1, s2 = Socket.pair(Socket::AF_UNIX, 1, 0) - s1.puts("test") - s2.gets.should == "test\n" + it "ensures the returned sockets are connected" do + s1, s2 = Socket.pair(Socket::AF_UNIX, 1, 0) + s1.puts("test") + s2.gets.should == "test\n" + s1.close + s2.close + end + + it "returns an array of two sockets" do + begin + s1, s2 = Socket.pair(:UNIX, :STREAM) + + s1.should.instance_of?(Socket) + s2.should.instance_of?(Socket) + ensure s1.close s2.close end + end - it "returns an array of two sockets" do - begin - s1, s2 = Socket.pair(:UNIX, :STREAM) + describe 'using an Integer as the 1st and 2nd argument' do + it 'returns two Socket objects' do + s1, s2 = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM) - s1.should.instance_of?(Socket) - s2.should.instance_of?(Socket) - ensure - s1.close - s2.close - end + s1.should.instance_of?(Socket) + s2.should.instance_of?(Socket) + s1.close + s2.close end + end - describe 'using an Integer as the 1st and 2nd argument' do - it 'returns two Socket objects' do - s1, s2 = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM) + describe 'using a Symbol as the 1st and 2nd argument' do + it 'returns two Socket objects' do + s1, s2 = Socket.pair(:UNIX, :STREAM) - s1.should.instance_of?(Socket) - s2.should.instance_of?(Socket) - s1.close - s2.close - end + s1.should.instance_of?(Socket) + s2.should.instance_of?(Socket) + s1.close + s2.close end - describe 'using a Symbol as the 1st and 2nd argument' do - it 'returns two Socket objects' do - s1, s2 = Socket.pair(:UNIX, :STREAM) - - s1.should.instance_of?(Socket) - s2.should.instance_of?(Socket) - s1.close - s2.close - end - - it 'raises SocketError for an unknown address family' do - -> { Socket.pair(:CATS, :STREAM) }.should.raise(SocketError) - end + it 'raises SocketError for an unknown address family' do + -> { Socket.pair(:CATS, :STREAM) }.should.raise(SocketError) + end - it 'raises SocketError for an unknown socket type' do - -> { Socket.pair(:UNIX, :CATS) }.should.raise(SocketError) - end + it 'raises SocketError for an unknown socket type' do + -> { Socket.pair(:UNIX, :CATS) }.should.raise(SocketError) end + end - describe 'using a String as the 1st and 2nd argument' do - it 'returns two Socket objects' do - s1, s2 = Socket.pair('UNIX', 'STREAM') + describe 'using a String as the 1st and 2nd argument' do + it 'returns two Socket objects' do + s1, s2 = Socket.pair('UNIX', 'STREAM') - s1.should.instance_of?(Socket) - s2.should.instance_of?(Socket) - s1.close - s2.close - end + s1.should.instance_of?(Socket) + s2.should.instance_of?(Socket) + s1.close + s2.close + end - it 'raises SocketError for an unknown address family' do - -> { Socket.pair('CATS', 'STREAM') }.should.raise(SocketError) - end + it 'raises SocketError for an unknown address family' do + -> { Socket.pair('CATS', 'STREAM') }.should.raise(SocketError) + end - it 'raises SocketError for an unknown socket type' do - -> { Socket.pair('UNIX', 'CATS') }.should.raise(SocketError) - end + it 'raises SocketError for an unknown socket type' do + -> { Socket.pair('UNIX', 'CATS') }.should.raise(SocketError) end + end - describe 'using an object that responds to #to_str as the 1st and 2nd argument' do - it 'returns two Socket objects' do - family = mock(:family) - type = mock(:type) + describe 'using an object that responds to #to_str as the 1st and 2nd argument' do + it 'returns two Socket objects' do + family = mock(:family) + type = mock(:type) - family.stub!(:to_str).and_return('UNIX') - type.stub!(:to_str).and_return('STREAM') + family.stub!(:to_str).and_return('UNIX') + type.stub!(:to_str).and_return('STREAM') - s1, s2 = Socket.pair(family, type) + s1, s2 = Socket.pair(family, type) - s1.should.instance_of?(Socket) - s2.should.instance_of?(Socket) - s1.close - s2.close - end + s1.should.instance_of?(Socket) + s2.should.instance_of?(Socket) + s1.close + s2.close + end - it 'raises TypeError when #to_str does not return a String' do - family = mock(:family) - type = mock(:type) + it 'raises TypeError when #to_str does not return a String' do + family = mock(:family) + type = mock(:type) - family.stub!(:to_str).and_return(Socket::AF_UNIX) - type.stub!(:to_str).and_return(Socket::SOCK_STREAM) + family.stub!(:to_str).and_return(Socket::AF_UNIX) + type.stub!(:to_str).and_return(Socket::SOCK_STREAM) - -> { Socket.pair(family, type) }.should.raise(TypeError) - end + -> { Socket.pair(family, type) }.should.raise(TypeError) + end - it 'raises SocketError for an unknown address family' do - family = mock(:family) - type = mock(:type) + it 'raises SocketError for an unknown address family' do + family = mock(:family) + type = mock(:type) - family.stub!(:to_str).and_return('CATS') - type.stub!(:to_str).and_return('STREAM') + family.stub!(:to_str).and_return('CATS') + type.stub!(:to_str).and_return('STREAM') - -> { Socket.pair(family, type) }.should.raise(SocketError) - end + -> { Socket.pair(family, type) }.should.raise(SocketError) + end - it 'raises SocketError for an unknown socket type' do - family = mock(:family) - type = mock(:type) + it 'raises SocketError for an unknown socket type' do + family = mock(:family) + type = mock(:type) - family.stub!(:to_str).and_return('UNIX') - type.stub!(:to_str).and_return('CATS') + family.stub!(:to_str).and_return('UNIX') + type.stub!(:to_str).and_return('CATS') - -> { Socket.pair(family, type) }.should.raise(SocketError) - end + -> { Socket.pair(family, type) }.should.raise(SocketError) end + end - it 'accepts a custom protocol as an Integer as the 3rd argument' do - s1, s2 = Socket.pair(:UNIX, :STREAM, Socket::IPPROTO_IP) - s1.should.instance_of?(Socket) - s2.should.instance_of?(Socket) + it 'accepts a custom protocol as an Integer as the 3rd argument' do + s1, s2 = Socket.pair(:UNIX, :STREAM, Socket::IPPROTO_IP) + s1.should.instance_of?(Socket) + s2.should.instance_of?(Socket) + s1.close + s2.close + end + + it 'connects the returned Socket objects' do + s1, s2 = Socket.pair(:UNIX, :STREAM) + begin + s1.write('hello') + s2.recv(5).should == 'hello' + ensure s1.close s2.close end - - it 'connects the returned Socket objects' do - s1, s2 = Socket.pair(:UNIX, :STREAM) - begin - s1.write('hello') - s2.recv(5).should == 'hello' - ensure - s1.close - s2.close - end - end end end diff --git a/spec/ruby/library/socket/socket/socket_spec.rb b/spec/ruby/library/socket/socket/socket_spec.rb index 5a3d6733e01e87..67dee75fae7aaa 100644 --- a/spec/ruby/library/socket/socket/socket_spec.rb +++ b/spec/ruby/library/socket/socket/socket_spec.rb @@ -20,19 +20,15 @@ UDPSocket.superclass.should == IPSocket end - platform_is_not :windows do - it "has a UNIXSocket in parallel to Socket" do - Socket.ancestors.include?(UNIXSocket).should == false - UNIXSocket.ancestors.include?(Socket).should == false - UNIXSocket.superclass.should == BasicSocket - end + it "has a UNIXSocket in parallel to Socket" do + Socket.ancestors.include?(UNIXSocket).should == false + UNIXSocket.ancestors.include?(Socket).should == false + UNIXSocket.superclass.should == BasicSocket end end -platform_is_not :windows do - describe "Server class hierarchy" do - it "contains UNIXServer" do - UNIXServer.superclass.should == UNIXSocket - end +describe "Server class hierarchy" do + it "contains UNIXServer" do + UNIXServer.superclass.should == UNIXSocket end end diff --git a/spec/ruby/security/cve_2018_8779_spec.rb b/spec/ruby/security/cve_2018_8779_spec.rb index 6d573ea7fd0d35..a8d65ce3031187 100644 --- a/spec/ruby/security/cve_2018_8779_spec.rb +++ b/spec/ruby/security/cve_2018_8779_spec.rb @@ -3,28 +3,26 @@ require 'socket' require 'tempfile' -platform_is_not :windows do - describe "CVE-2018-8779 is resisted by" do - before :each do - tmpfile = Tempfile.new("s") - @path = tmpfile.path - tmpfile.close(true) - end +describe "CVE-2018-8779 is resisted by" do + before :each do + tmpfile = Tempfile.new("s") + @path = tmpfile.path + tmpfile.close(true) + end - after :each do - File.unlink @path if @path && File.socket?(@path) - end + after :each do + File.unlink @path if @path && File.socket?(@path) + end - it "UNIXServer.open by raising an exception when there is a NUL byte" do - -> { - UNIXServer.open(@path+"\0") - }.should.raise(ArgumentError, /(path name|string) contains null byte/) - end + it "UNIXServer.open by raising an exception when there is a NUL byte" do + -> { + UNIXServer.open(@path+"\0") + }.should.raise(ArgumentError, /(path name|string) contains null byte/) + end - it "UNIXSocket.open by raising an exception when there is a NUL byte" do - -> { - UNIXSocket.open(@path+"\0") - }.should.raise(ArgumentError, /(path name|string) contains null byte/) - end + it "UNIXSocket.open by raising an exception when there is a NUL byte" do + -> { + UNIXSocket.open(@path+"\0") + }.should.raise(ArgumentError, /(path name|string) contains null byte/) end end From 6faa43eb0095df6f096ce2953ced9c421b4987f9 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 12 Sep 2026 10:02:52 -0500 Subject: [PATCH 09/11] [DOC] Harmonize lutime doc --- file.c | 3 ++- pathname_builtin.rb | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/file.c b/file.c index 112cf393e88395..c991bc8bd826b6 100644 --- a/file.c +++ b/file.c @@ -3614,7 +3614,8 @@ rb_file_s_utime(int argc, VALUE *argv, VALUE _) * call-seq: * File.lutime(atime, mtime, *paths) -> path_count * - * Like File#utime, but does not follow symbolic links, + * Like File::utime, + * but does not follow [symbolic links](rdoc-ref:file/symbolic_links.md), * and therefore changes the times of the entries given by `paths`, * regardless of whether they are symbolic links; * returns the number of `paths` given: diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 32a8234f79093b..edf43d85330df2 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -1972,7 +1972,8 @@ def utime(atime, mtime) File.utime(atime, mtime, @path) end # call-seq: # lutime(atime, mtime) -> 1 # - # Like Pathname#utime, but does not follow symbolic links, + # Like Pathname#utime, + # but does not follow [symbolic links](rdoc-ref:file/symbolic_links.md), # and therefore changes the times of the entry in `self`, # regardless of whether it is a symbolic link: # From b49ac40e4427ee2175ff2d7dc951fd55da330748 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 12 Sep 2026 14:25:45 -0500 Subject: [PATCH 10/11] [DOC] Harmonize rename methods --- doc/file/symbolic_links.md | 2 +- file.c | 67 +++++++++++++++++++++++++++++++++++--- pathname_builtin.rb | 59 +++++++++++++++++++++++++-------- 3 files changed, 108 insertions(+), 20 deletions(-) diff --git a/doc/file/symbolic_links.md b/doc/file/symbolic_links.md index 74df11f5d8111b..36c85f80dd8803 100644 --- a/doc/file/symbolic_links.md +++ b/doc/file/symbolic_links.md @@ -197,7 +197,7 @@ File.delete(linkpath) ### `rename` -Each of these methods changes the name of an entry (which need not be a symlink): +Each of these methods changes the name of an entry (which may be a symlink): - File::rename - Pathname#rename diff --git a/file.c b/file.c index c991bc8bd826b6..495f564312f0e7 100644 --- a/file.c +++ b/file.c @@ -3915,13 +3915,70 @@ no_gvl_rename(void *ptr) } /* - * call-seq: - * File.rename(old_name, new_name) -> 0 + * :markup: markdown + * + * call-seq: + * File.rename(path, new_path) -> 0 + * + * Moves the entry at the given `path` to the given `new_path`. + * + * Does not follow [symbolic links](rdoc-ref:file/symbolic_links.md); + * if the entry is a symlink, the link itself is renamed. + * + * The examples below use two temporary directories: + * + * ```ruby + * src_dirpath = '/tmp/src/' # => "/tmp/src/" + * dst_dirpath = '/tmp/dst/' # => "/tmp/dst/" + * Dir.mkdir(src_dirpath) + * Dir.mkdir(dst_dirpath) + * ``` + * + * The entry to be renamed may be a file: + * + * ```ruby + * src_filepath = File.join(src_dirpath, 't.tmp') # => "/tmp/src/t.tmp" + * File.write(src_filepath, 'foo') + * dst_filepath = File.join(dst_dirpath, 'u.tmp') # => "/tmp/dst/u.tmp" + * File.rename(src_filepath, dst_filepath) + * File.exist?(src_filepath) # => false + * File.exist?(dst_filepath) # => true + * File.delete(dst_filepath) # Clean up. + * ``` + * + * The entry to be renamed may be a symbolic link: + * + * ```ruby + * filepath = File.join(src_dirpath, 't.tmp') # => "/tmp/src/t.tmp" + * File.write(src_filepath, 'foo') + * linkpath = File.join(src_dirpath, 'u.tmp') # => "/tmp/src/u.tmp" + * File.symlink(filepath, linkpath) + * File.readlink(linkpath) # => "/tmp/src/t.tmp" + * newpath = File.join(dst_dirpath, 'v.tmp') # => "/tmp/dst/v.tmp" + * File.rename(linkpath, newpath) # Symlink not followed. + * File.readlink(newpath) # => "/tmp/src/t.tmp" + * File.delete(filepath, newpath) # Clean up. + * ``` + * + * The entry to be renamed may be a directory: * - * Renames the given file to the new name. Raises a SystemCallError - * if the file cannot be renamed. + * ```ruby + * old_dirpath = File.join(src_dirpath, 'olddir') # => "/tmp/src/olddir" + * Dir.mkdir(old_dirpath) + * new_dirpath = File.join(dst_dirpath, 'newdir') # => "/tmp/dst/newdir" + * File.rename(old_dirpath, new_dirpath) + * File.directory?(new_dirpath) # => true + * Dir.rmdir(new_dirpath) # Clean up. + * ``` + * + * Clean up: + * + * ```ruby + * FileUtils.rm_rf(src_dirpath) # => ["/tmp/src/"] + * FileUtils.rm_rf(dst_dirpath) # => ["/tmp/dst/"] + * ``` * - * File.rename("afile", "afile.bak") #=> 0 + * Raises SystemCallError if the file cannot be renamed. */ static VALUE diff --git a/pathname_builtin.rb b/pathname_builtin.rb index edf43d85330df2..2d77eda8f9e235 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -1786,18 +1786,27 @@ def readlink() self.class.new(File.readlink(@path)) end # :markup: markdown # # call-seq: - # rename(new_name) + # rename(new_path) -> 0 # - # Renames the entry at the path in `self` to the entry given in `new_name`, - # which may be either a path or another pathname: + # Moves the entry at the path in `self` to the given `new_path`, + # which may be either a path or another pathname. + # + # Does not follow [symbolic links](rdoc-ref:file/symbolic_links.md); + # if the entry is a symlink, the link itself is renamed. + # + # The examples below use two temporary directories: # # ```ruby - # # Create source and destination pathnames and directories. - # pn_srcdir = Pathname('/tmp/src') # => # + # pn_srcdir = Pathname('/tmp/src/') # => # + # pn_dstdir = Pathname('/tmp/dst/') # => # # pn_srcdir.mkdir - # pn_dstdir = Pathname('/tmp/dst') # => # # pn_dstdir.mkdir - # # Create source file pathname and file. + # ``` + # + # The entry to be renamed may be a file: + # + # ```ruby + # # Create source pathname and file. # pn_srcfile = pn_srcdir.join('t.tmp') # => # # pn_srcfile.write('foo') # # Create destination file pathname. @@ -1806,23 +1815,45 @@ def readlink() self.class.new(File.readlink(@path)) end # pn_srcfile.rename(pn_dstfile) # pn_srcfile.exist? # => false # pn_dstfile.exist? # => true + # pn_srcfile # => # # Not changed. + # pn_dstfile.delete # Clean up. + # ``` + # + # The entry to be renames may be a symbolic link: + # + # ```ruby + # # Create source pathname and file. + # pn_srcfile = pn_srcdir.join('t.tmp') # => # + # pn_srcfile.write('foo') + # # Create link pathname and link. + # pn_lnkfile = pn_dstdir.join('u.tmp') pn_lnkfile = pn_dstdir.join('u.tmp') + # pn_lnkfile.make_symlink(pn_srcfile) + # pn_lnkfile.readlink # => # + # pn_renamed = Pathname('lib/v.tmp') # => # + # pn_lnkfile.rename(pn_renamed) # Symlink not followed. + # pn_renamed.symlink? # => true + # pn_renamed.readlink # => # + # pn_lnkfile # => # # Not changed. + # # Clean up. + # pn_renamed.delete + # pn_srcfile.delete # ``` # - # Works for directories, too: + # The entry to be renamed may be a directory: # # ```ruby - # pn_dstdir.rename('/tmp/foo') - # pn_dstdir.exist? # => false - # Pathname('/tmp/foo').exist? # => true + # pn_renamed = Pathname('/tmp/foo') # => # + # pn_dstdir.rename(pn_renamed) # ``` # - # Clean up. + # Clean up: # # ```ruby - # pn_srcdir.rmtree - # Pathname('/tmp/foo').rmtree + # pn_renamed.rmtree # => # + # pn_srcdir.rmtree # => # # ``` # + # # Raises SystemCallError if the entry cannot be renamed. def rename(to) File.rename(@path, to) end From 7463f4d7975e5881b2868e4d4bf8f1dd6a7a17d8 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 12 Sep 2026 09:55:02 -0500 Subject: [PATCH 11/11] [DOC] Harmonize lchown doc --- file.c | 3 ++- pathname_builtin.rb | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/file.c b/file.c index 495f564312f0e7..f7d0a46420d4f2 100644 --- a/file.c +++ b/file.c @@ -3358,7 +3358,8 @@ lchown_internal(const char *path, void *arg) * * Calling process must have superuser privileges. * - * When supported: like File::chown, but does not follow symbolic links, + * When supported: like File::chown, + * but does not follow [symbolic links](rdoc-ref:file/symbolic_links.md), * and therefore changes the ownership of the entries given by `paths`; * returns the number of paths given: * diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 2d77eda8f9e235..8728c521c7f4db 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -1624,7 +1624,8 @@ def chown(owner, group) File.chown(owner, group, @path) end # # Calling process must have superuser privileges. # - # When supported: like Pathname#chown, but does not follow symbolic links, + # When supported: like Pathname#chown, + # but does not follow [symbolic links](rdoc-ref:file/symbolic_links.md), # and therefore changes the ownership of the entry at the path in `self`: # # ```ruby