【一人 bugs.ruby Advent Calendar 2021】番外編: 今年みた Ruby のバグ報告【25日目】
一人 bugs.ruby Advent Calendar 2021 25日目の記事になります。
今日で Advent Calendar も最後という事で今回は今年みた Ruby のバグをいくつか紹介してみようと思います。
またこれから紹介する修正済みのバグは Ruby 3.1 ではなくて古い Ruby でもバックポートされている可能性があるので注意してください(〜で修正済みと書かれていても RUby 2.7.x 系でバックポートされていて修正済みの可能性があります。
[Bug #18377] Integer#times has different behavior depending on the size of the integer
Integer#+
を書き換えると特定の値で Integer#times
の挙動に影響を与えるというバグ報告です。
# これは問題がない (2**1).times do Integer.undef_method(:+) Integer.define_method(:+) do |_other| puts "my custom add" end end # FIXNUM を越える値に対して `times` を呼び出すと Integer#+ を呼び出してエラーになる # `times': undefined method `<' for nil:NilClass (NoMethodError) (2**65).times do Integer.undef_method(:+) Integer.define_method(:+) do |_other| # ここが呼び出されるようになる puts "my custom add" end end
内部で Integer#+
を呼び出すようになっていたところを呼び出さないようにして対応済みです。
[Bug #17675] StringIO#each_byte doesn't check for readabilty while iterating
IO
がクローズしているのにイテレーションが処理されてしまうというバグ報告です。
require "stringio" strio = StringIO.new("1234") strio.each_byte do |byte| puts byte # ここでクローズしているがイテレーションは引き続き処理されている strio.close end # => 49 # 50 # 51 # 52
この問題は修正済みで最新版では IOError
が発生するようになります。
require "stringio" strio = StringIO.new("1234") strio.each_byte do |byte| puts byte strio.close end # => 49 # error: `each_byte': not opened for reading (IOError)
[Bug #18343] empty hash passed to Array#pack causes Segmentation fault (2.6)
Ruby 2.4 ~ 2.6 で Array#pack
に空の Hash
を渡すと Segmentation fault が発生するというバグ報告です。
# これで segv する [0].pack('c', {})
この問題は Ruby 2.7 以降では再現せず Ruby 2.6 は現状セキリュティサポートのみなのでこのチケットは閉じられています。
[Bug #18292] 3.1.0-dev include
cause Module to be marked as initialized
これは Ruby 3.1.0-dev で発生したバグになります。
次のように Module
を継承して include
した後に super
を呼ぶとエラーになってしまうバグです。
class Mod1 < Module def initialize(...) super end end p Mod1.new # => #<Mod1:0x000055b6dc5a5d00> class Mod2 < Module def initialize(...) include Enumerable super end end p Mod2.new # 3.0.2 => #<Mod2:0x000055b6dc5a59e0> # 3.1.0-dev => error: `initialize': already initialized module (TypeError)
以下のコードだけでもエラーになったので include
後に Module#initialize
を呼ぶとダメなのかも?
class Mod2 < Module def initialize(...) include Enumerable super end end p Mod2.new # 3.0.2 => #<Mod2:0x000055b6dc5a59e0> # 3.1.0-dev => error: `initialize': already initialized module (TypeError)
これはまだ最新の 3.1.0-dev でも再現していたので Ruby 3.1 でも残ったままになってしまっているかも。
[Bug #18329] Calling super to non-existent method dumps core
存在しない super
を呼び出すとコアダンプするというバグ報告です。
次のコードを Ruby 3.0.2 で実行すると segv します。
module Probes def self.included(base) base.extend(ClassMethods) end module ClassMethods def probe(*methods) prepend(probing_module(methods)) end def probing_module(methods) Module.new do methods.each do |method| define_method(method) do |*args, **kwargs, &block| super(*args, **kwargs, &block) end end end end end end class Probed include Probes probe :danger!, :missing def danger! raise "BOOM" end end 5.times do subject = Probed.new subject.danger! rescue RuntimeError subject.missing rescue NoMethodError end
ちょっと分かりづらいので最小構成にすると以下のような感じです。
class Probed def self.probing_module(methods) Module.new do methods.each do |method| define_method(method) do |*args, **kwargs, &block| super(*args, **kwargs, &block) end end end end prepend probing_module [:danger!, :missing] def danger! end end subject = Probed.new subject.danger! # ここで存在しない super を呼び出している subject.missing rescue NoMethodError # ここで segv subject.danger!
この問題は既に修正されていて Ruby 3.0.3 にもバックポートされています。
[Bug #18243] Ractor.make_shareable does not freeze the receiver of a Proc but allows accessing ivars of it
次のように Ractor
内で別の Ractor
のオブジェクトが書き換えられてしまうバグ報告です。
class C attr_accessor :foo def setter_proc Ractor.make_shareable(proc {}) # Ractor.make_shareable(-> v { @foo = v }) end end c = C.new c.foo = 1 p c # => #<C:0x0000559bf1df5880 @foo=1> # インスタンス変数を書き換える proc を生成 # c 自体は freeze されてない proc = c.setter_proc p c.frozen? # => false # Ractor 内で setter_proc を呼び出すと c のオブジェクトが書き換えら得てしまう # これがバグ Ractor.new(proc) { |s| s.call(42) }.take p c # => #<C:0x0000559bf1df5880 @foo=42>
この問題は修正済みで生成 Proc
の self
が共有可能オブジェクトかどうかまで参照するようになりました。
# OK: self は参照可能オブジェクトである Ractor.make_shareable(nil.instance_eval { -> {} }) # NG: self は参照可能オブジェクトではない # error: `make_shareable': Proc's self is not shareable: #<Proc:0x00007fb6e26e5460 /tmp/vDlYFAi/48:5 (lambda)> (Ractor::IsolationError) Ractor.make_shareable(-> {})
[Bug #17719] Irregular evaluation order in hash literals
Hash
リテラルで同名のキーが存在する場合に評価順が左からにならないバグ報告です。
これは Ruby 3.1 で修正されました。
ary = [] { a: ary << 1, b: ary << 2, a: ary << 3 } pp ary # Ruby 3.0 => [1, 3, 2] # Ruby 3.1 => [1, 2, 3]
[Bug #1823] Conversion to float not working for object with to_f method
Thread#join
で引数を Float
に変換しているが #to_f
が呼ばれていないというバグ報告です。
Ruby 3.0 以降から再現するようになりました。
class Something def to_f 0.1 end end # error: `join': can't convert Something into Float (TypeError) Thread.new{ }.join(Something.new)
この問題は Ruby 3.1 で修正済みです。
[Bug #18180] opt_newarray_min/max instructions ignore refined methods
以下のようなケースで Refinements が正しく反映されていないというバグ報告です。
module M refine Array do def min; :min; end def max; :max; end end end using M # これは Refinements が適用される pp [1, 2, 3].min # => :min # これは Refinements が適用されない pp [1+0, 2, 3].min # => 1
これはレシーバがリテラルの場合に最適化を行っていて Refinements が反映されなくなってしまっているが原因らしいです。
これは Ruby 3.1 で修正済みです。
module M refine Array do def min; :min; end def max; :max; end end end using M pp [1+0, 2, 3].min # Ruby 3.0 => 1 # Ruby 3.1 => :min
[Bug #17048] Calling initialize_copy on live modules leads to crashes
以下のコードで Ruby がクラッシュするというバグ報告です。
loop do m = Module.new do prepend Module.new def hello end end klass = Class.new { include m } m.send(:initialize_copy, Module.new) GC.start klass.new.hello rescue nil end
上記のように Module#initialize_copy
を呼び出すとクラッシュする可能性があるらしいです。
Ruby 3.1 では Module#initialize_copy
を呼び出すと TypeError
が発生するように修正されました。
module A end # error: `initialize_copy': already initialized module (TypeError) A.send(:initialize_copy, Module.new) # fine, no one inherits from A
[Bug #18160] IndexError raised from MatchData#{offset,begin,end} does not keep the encoding of the argument
MatchData#{offset,begin,end}
で発生した IndexError
がエンコーディングを保持してないバグ報告です。
pp RUBY_VERSION # => "3.0.2" m = /.*/.match("foo") m.offset("\u{3042}") rescue p $!.message # => "undefined group name reference: \xE3\x81\x82"
これは修正されて Ruby 3.0.3 以降では意図する文字コードで出力されます。
pp RUBY_VERSION # => "3.0.3" m = /.*/.match("foo") m.offset("\u{3042}") rescue p $!.message # => "undefined group name reference: あ"
[Bug #18084] JSON.dump
can crash VM.
次のように再帰的な Hash
を JSON.dump
に渡すと VM がクラッシュするバグ報告です。
require 'json' x = {} # 自身に自身を割り当てる x[:x] = x # machine stack overflow in critical region (fatal) p JSON.dump(x)
これは Ruby 2.7 から再現しており、Ruby 2.7 以前は SystemStackError
が発生していました。
require 'json' x = {} x[:x] = x # Ruby 2.6 の場合 # error: stack level too deep (SystemStackError) p JSON.dump(x)
これは修正されて Ruby 3.1 からは SystemStackError
が発生します。
[Bug #18080] Syntax error on one-line pattern matching
次のように『カッコで囲まれていないパラメータを持つメソッドの戻り値を右代入で使用するとシンタックスエラーになる』というバグ報告です。
# パラメータがなかったり、カッコが付いている場合は OK p do end => a p a #=> nil p(1) do end => a p a #=> 1 # カッコがないパラメータがある場合はシンタックスエラーになる p 1 do end => a #=> # syntax error, unexpected =>, expecting end-of-input # end => a # ^~ # これは1行 in でも同様にシンタックスエラーになる p 1 do end in a #=> # syntax error, unexpected `in', expecting end-of-input # end in a # ^~
このエラーは意図的ではないが修正するのは難しいらしいです。
[Bug #18053] Crashes and infinite loops when generating partial backtraces in Ruby 3.0+
以下のコードを Ruby 3.0 以降で実行すると segv するというバグ報告です。
def foo caller_locations(2, 1).inspect # this will segv # caller_locations(2, 1)[0].path # this will infinite loop end 1.times.map { 1.times.map { foo } }
これは Ruby 3.0 の最適化のバグらしいです。
Ruby 3.0.3 以降では修正済みです。
[Bug #18031] Nested TracePoint#enable with target crashes
以下のように TracePoint
がネストしているとクラッシュするというバグ報告です。
one = TracePoint.new(:call) {} two = TracePoint.new(:call) {} obj = Object.new obj.define_singleton_method(:foo) {} # a bmethod foo = obj.method(:foo) # ここでクラッシュする one.enable(target: foo) do two.enable(target: foo) {} end
修正 PR は既にあるんですが、いくつか問題がありまだマージされていません。
[Bug #16243 完了] case/when is slower than if on MRI
Ruby 2.6.5 で if
文よりも case when
文の方が遅いというバグ報告です。
# frozen_string_literal: true require "benchmark/ips" def deep_dup_case(obj) case obj when Integer, Float, TrueClass, FalseClass, NilClass obj when String obj.dup when Array obj.map { |e| deep_dup_case(e) } when Hash duped = obj.dup duped.each_pair do |key, value| duped[key] = deep_dup_case(value) end else obj.dup end end def deep_dup_if(obj) if Integer === obj || Float === obj || TrueClass === obj || FalseClass === obj || NilClass === obj obj elsif String === obj obj.dup elsif Array === obj obj.map { |e| deep_dup_if(e) } elsif Hash === obj duped = obj.dup duped.each_pair do |key, value| duped[key] = deep_dup_if(value) end duped else obj.dup end end obj = { "class" => "FooWorker", "args" => [1, 2, 3, "foobar"], "jid" => "123987123" } Benchmark.ips do |x| x.report("deep_dup_case") do deep_dup_case(obj) end x.report("deep_dup_if") do deep_dup_if(obj) end x.compare! end __END__ Warming up -------------------------------------- deep_dup_case 37.767k i/100ms deep_dup_if 41.802k i/100ms Calculating ------------------------------------- deep_dup_case 408.046k (± 0.9%) i/s - 2.077M in 5.090997s deep_dup_if 456.657k (± 0.9%) i/s - 2.299M in 5.035040s Comparison: deep_dup_if: 456657.4 i/s deep_dup_case: 408046.1 i/s - 1.12x slower
この問題は Ruby 3.1 で改善されています。
[Bug #14817] TracePoint#parameters for bmethod's return event should return the same value as its Method#parameters
TracePoint
の :return
イベント時に TracePoint#parameters
で正しく値が取得できないバグ報告です。
define_method(:bm) {|a|} p method_parameters: method(:bm).parameters # => {:method_parameters=>[[:req, :a]]} trace = TracePoint.new(:call, :return){|tp| mid = tp.method_id if mid == :bm p mid: mid, event: tp.event, tp_parameters: tp.parameters end } trace.enable{ bm(0) } # :call 時は parameters が取得できているが # :return 時は parameters が取得できてない # output: # {:mid=>:bm, :event=>:call, :tp_parameters=>[[:req, :a]]} # {:mid=>:bm, :event=>:return, :tp_parameters=>[]}
TracePoint#parameters
だけではなくて define_method
+ TracePoint
全般の問題らしいです。
define_method(:bm) {|a|} trace = TracePoint.new(:call, :return){|tp| p [tp.event, tp.lineno] if tp.method_id == :bm } trace.enable{ bm(0) } # output: # [:call, 1] # [:return, 7] #=> [:return, 1] になるべき?
この問題は Ruby 3.1 で修正済みです。
[Bug #14391] Integer#digitsが遅い
Integer#digits
が遅いというバグ報告です。
Integer#to_s
と比較してもかなり遅いらしい。
(9999**9999).to_s.chars.map(&:to_i).reverse # 0.030225秒 (9999**9999).digits # 1.187126秒 (40倍) (99999**99999).to_s.chars.map(&:to_i).reverse # 1.888218秒 (99999**99999).digits # 195.594539秒 (100倍)
ちなみに Integer#digits
は各桁を配列として返すメソッドになります。
pp 16.digits # => [6, 1] pp 1234.digits # => [4, 3, 2, 1]
この問題は Ruby 3.1 で修正済みです。
[Bug #17767] Cloned ENV
inconsistently returns ENV
or self
ENV
と ENV.clone
したオブジェクトで挙動に一貫性がないというバグ報告です。
cloned_env = ENV.clone p ENV.each_key{}.equal?(ENV) #=> true p cloned_env.each_key{}.equal?(cloned_env) #=> true ENV.delete('TEST') err = ENV.fetch('TEST') rescue $! p err.receiver.equal?(ENV) #=> true err = cloned_env.fetch('TEST') rescue $! p err.receiver.equal?(cloned_env) #=> false ENV['TEST'] = 'TRUE' p ENV.select!{ false }.equal?(ENV) #=> true cloned_env['TEST'] = 'TRUE' p cloned_env.select!{ false }.equal?(cloned_env) #=> false
このチケットがきっかけで Ruby 3.1 から以下のように挙動が変わりました。
ENV.dup
を使うと例外が発生するENV.clone
を使うと警告がでる- 詳細 : https://blog.n-z.jp/blog/2021-06-12-ruby-env.html
[Bug #17951] Collisions in Proc#hash values for blocks defined at the same line
Proc#hash
の値が同じ値になるケースがあるというバグ報告です。
require 'set' def capture(&block) block end # 同じブロックを大量に生成する blocks = Array.new(1000) { capture { :foo } } hashes = blocks.map(&:hash).uniq ids = blocks.map(&:object_id).uniq equality = blocks.map { blocks[0].eql?(_1) }.tally hash = blocks.to_h { [_1, nil] } set = blocks.to_set # hash が一意であれば hashes.size == 1000 になるはずだがなっていない puts(hashes.size) # => 11 puts(ids.size) # => 1000 puts(equality.inspect) # => {true=>1, false=>999} puts(hash.size) # => 1000 puts(set.size) # => 1000
この問題は Ruby 3.1 で修正済みです。
[Bug #17889] Enumerator::Lazy#with_index should return size
Enumerator::Lazy#with_index
の戻り値に対して size
を呼ぶと意図しない値が返ってきたというバグ報告です。
p Enumerator::Lazy.new([1, 2, 3], 3){|y, v| y << v}.with_index.size # 期待する値 => 3 # 実際の値 => nil
この問題は Ruby 3.0.2 で修正済みです。
[Bug #17857] when 0r
and when 0i
do not match with case 0
0r === 0
や 0i === 0
は true
を返すが case-when でマッチしないというバグ報告です。
# これは true を返す p 0r === 0 # => true p 0i === 0 # => true # しかし case-when では 0r などにマッチしない case 0 when 0r p :hoge when 0i p :foo else p :bar end # 期待する挙動 => :hoge # 実際の挙動 => :bar
これは最適化のバグらしく、最適化を無効にして実行すると問題なく動作します。
# 最適化を無効にして Ruby のコードを実行する RubyVM::InstructionSequence.compile(<<END, specialized_instruction: false).eval case 0 when 0r p :hoge when 0i p :foo else p :bar end # => :hoge END
この問題は Ruby 3.1 で修正済みです。
[Bug #17814] inconsistent Array.zip behavior
以下のように Array#zip
だとイテレーションが1回余計に呼ばれているというバグ報告です。
i = 0 # 1 ずつ増えるカウンタ e = Enumerator.produce { i += 1 } # 1つ余計にイテレーションが発生する p [0, 0, 0, 0].zip e # => [[0, 1], [0, 2], [0, 3], [0, 4]] p i # 期待する挙動 => 4 # 実際の挙動 => 5 # Enumerable#zip だと再現しない p [0, 0, 0, 0].each.zip e # => [[0, 6], [0, 7], [0, 8], [0, 9]] p i # => 9
Enumerable#zip
だと問題ないので対応する場合はこっちを使うとよさそう。
この問題は Ruby 3.1 で修正済みです。
[Bug #4443] odd evaluation order in a multiple assignment
以下のように多重代入した時に先に右辺のメソッドが呼び出されるというバグ報告です。
def foo p :foo [] end def bar p :bar end # bar -> foo という順に評価される x, foo[0] = bar, 0 # output: # :bar # :foo # これは foo -> bar という順になる foo[0] = bar # output: # :foo # :bar
10年前のチケットで Ruby 3.1 で修正されました。
Ruby 3.1 だと以下のような挙動になります。
def foo p :foo [] end def bar p :bar end # Ruby 3.1だと foo -> bar と評価されるようになった x, foo[0] = bar, 0 # output: # :foo # :bar
[Bug #17754] NoMethodError#to_s makes segmentation fault when Module#name returns non string value
以下のように .name
が文字列以外を返した場合に SEGV するというバグ報告です。
class C def self.name 42 # これなら OK # "42" end end # C に対して NoMethodError なエラーが発生すると SEGV する C.this_method_does_not_exist
これは Ruby 3.0.0 で再現し、Ruby 3.0.1 では修正済みです。
[Bug #17756] StringScanner#charpos makes segmentation fault when target.byteslice returns non string value
以下のように StringScanner
を使用すると SEGV するというバグ報告です。
require 'strscan' string = 'ruby' scnanner = StringScanner.new(string) pre = Module.new do def byteslice(*args) end end string.singleton_class.prepend(pre) scnanner.charpos
この問題は Ruby 3.0.3 で修正済みです。
[Bug #17739] Array#sort! changes the order even if the receiver raises FrozenError in given block
Array#sort!
のブロック内でレシーバを freeze
すると例外が発生するがソート済みになっているというバグ報告です。
array = [1, 2, 3, 4, 5] begin array.sort! do |a, b| array.freeze if a == 3 1 end rescue => err # 例外が発生する p err #=> #<FrozenError: can't modify frozen Array: [5, 4, 3, 2, 1]> end # 例外が発生してもソート済みになっている p array #=> [5, 4, 3, 2, 1]
ちなみに break
した場合はそこまでのソートになっている
array = [1, 2, 3, 4, 5] array.sort! do |a, b| break if a == 3 1 end # 途中までソートされた状態 p array #=> [3, 4, 2, 1, 5]
この問題は Ruby 3.1 で修正済みです。
[Bug #17719] Irregular evaluation order in hash literals
Hash
リテラルでキーが重複している場合に以下のような評価順になるというバグ報告です。
{ foo: p(1), bar: p(2), foo: p(3) } # => 1 # 3 # 2
Ruby では左から右に評価されるのが一般的なので Ruby 3.1 では左から右に向かって評価されるように修正されました。
{ foo: p(1), bar: p(2), foo: p(3) } # => 1 # 2 # 3
[Bug #17652] GC compaction crash on mprotect
以下のコードを実行した時に GC compaction でクラッシュするというバグ報告です。
GC.auto_compact = true times = 20_000_000 arr = Array.new(times) times.times do |i| arr[i] = "#{i}" end arr = Array.new(1_000_000, 42) GC.start puts "ok"
この問題は Ruby 3.1 で修正済みです。
[Bug #17661] IO#each will segfault when if file is closed inside an each_byte
block
以下のように File#each_byte
内で File#close
すると segv するというバグ報告です。
file = File.open(__FILE__) file.each_byte do |byte| file.close end
この問題は Ruby 3.0.3 で修正済みです。
[Bug #17667] Module#name needs synchronization
Module#name
は非同期処理に対応していないので以下のようにすると segv するというバグ報告です。
class C @iv = 1 end Ractor.new { loop { C.name } } class C 0.step { |i| instance_variable_set("@iv#{i}", i) } end
この問題はまだ未修正のようです。
[Bug #17649] defined?
invokes method once for each syntactic element around it
defined?
の式で複数回メソッドが呼ばれることがあるというバグ報告です。
以下の例だと x
メソッドが defined?
時に複数回呼ばれることがあるらしい。
public def x $times_called += 1 end def times_called $times_called = 0 yield $times_called end # without `defined?` times_called { x } # => 1 times_called { -x } # => 1 times_called { --x } # => 1 times_called { ---x } # => 1 times_called { x+0+0 } # => 1 times_called { x.pred.pred } # => 1 times_called { x.x.x.x.x.x } # => 6 # with `defined?` times_called { defined? x } # => 0 times_called { defined? -x } # => 1 times_called { defined? --x } # => 2 times_called { defined? ---x } # => 3 times_called { defined? x+0+0 } # => 2 times_called { defined? x.pred.pred } # => 2 times_called { defined? x.x.x.x.x.x } # => 15
これは defined? a.b.c.d
を呼び出した時に a
a.b
a.b.c
a.b.c.d
が個別に呼び出されしまっているからのようです。
puts RubyVM::InstructionSequence.new("defined? a.b.c.d").disasm __END__ output: == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,16)> (catch: TRUE) == catch table | catch type: rescue st: 0001 ed: 0039 sp: 0000 cont: 0041 | == disasm: #<ISeq:defined guard in <compiled>@<compiled>:0 (0,0)-(-1,-1)> (catch: FALSE) | local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) | [ 1] $!@0 | 0000 putnil | 0001 leave |------------------------------------------------------------------------ 0000 putnil ( 1)[Li] 0001 putself 0002 defined func, :a, false 0006 branchunless 41 0008 putself 0009 opt_send_without_block <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE> 0011 defined method, :b, false 0015 branchunless 41 0017 putself 0018 opt_send_without_block <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE> 0020 opt_send_without_block <calldata!mid:b, argc:0, ARGS_SIMPLE> 0022 defined method, :c, false 0026 branchunless 41 0028 putself 0029 opt_send_without_block <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE> 0031 opt_send_without_block <calldata!mid:b, argc:0, ARGS_SIMPLE> 0033 opt_send_without_block <calldata!mid:c, argc:0, ARGS_SIMPLE> 0035 defined method, :d, true 0039 swap 0040 pop 0041 leave
この問題は Ruby 3.1 で修正済みです。
public def x $times_called += 1 end def times_called $times_called = 0 yield $times_called end p times_called { defined? x.x.x.x.x.x } # Ruby 3.0 => 15 # Ruby 3.1 => 5
[Bug #17590] M.prepend M
has hidden side effect
M.prepend M
を呼び出すとエラーになるが副作用があるというバグ報告です。
以下のように M.prepend M
を呼んだ場合とそうでない場合で差異があります。
module M; end class C; end C.prepend M C.include M module M2; end M2.prepend M C.include M2 # M.prepend M を呼んでない場合 p C.ancestors # => [M, C, M2, Object, Kernel, BasicObject] M.prepend M rescue nil module M3; end M3.prepend M C.include M3 # M.prepend M を呼んだ場合 # M が複数追加されている… p C.ancestors # => [M, C, M3, M, M3, M2, Object, Kernel, BasicObject]
これは Ruby 3.1 で修正済みです。
module M; end class C; end C.prepend M C.include M M.prepend M rescue nil module M2; end M2.prepend M C.include M2 p C.ancestors # Ruby 3.0 => [M, C, M2, M, M2, Object, Kernel, BasicObject] # Ruby 3.1 => [M, C, M2, Object, Kernel, BasicObject]
また、上記とは別に以下のように M.prepend M
すると M.ancestors # => [M, M]
となる問題もあってこれも Ruby 3.1 では修正済みです。
module M end # 継承リストは自身だけ pp M.ancestors # => [M] begin # 自身を prepend するとエラーになる # これは Ruby 2.7 でも 3.0 でも同じ M.prepend M rescue => e puts "error : #{e.message}" # => error : cyclic prepend detected end # M.prepend M はエラーになるが Ruby 3.0 では副作用がある # Ruby 3.1 では修正済み pp M.ancestors # Ruby 2.7 => [M] # Ruby 3.0 => [M, M] # Ruby 3.1 => [M]
[Bug #17554] [PATCH] Fix ObjectSpace.dump to include singleton class name
次のように ObjectSpace.dump
に特異クラスを渡した場合に Ruby 3.0 だと "name"
が含まれなくなっているというバグ報告です。
require "objspace" puts ObjectSpace.dump(Object.new.singleton_class) # 2.7 => {"address":"0x55adae76b630", "type":"CLASS", "class":"0x55adae7a76d0", "name":"Object", "references":["0x55adae7a9250", "0x55adae76b720"], "memsize":464, "flags":{"wb_protected":true}} # 3.0 => {"address":"0x55d9048e80e0", "type":"CLASS", "class":"0x55d90476d738", "references":["0x55d90476e8b8", "0x55d9048e8158"], "memsize":472, "flags":{"wb_protected":true}}
これは Ruby 3.1 でまた別の情報を返すようにして対応されたようです。
require "objspace" puts ObjectSpace.dump(Object.new.singleton_class) # Ruby 2.7 => {"address":"0x55d5a0f891e0", "type":"CLASS", "class":"0x55d5a0ffb6f0", "name":"Object", "references":["0x55d5a1001258", "0x55d5a0f89348"], "memsize":464, "flags":{"wb_protected":true}} # Ruby 3.0 => {"address":"0x55cfdc251690", "type":"CLASS", "class":"0x55cfdbf99718", "references":["0x55cfdbf9a898", "0x55cfdc251708"], "memsize":472, "flags":{"wb_protected":true}} # Ruby 3.1 => {"address":"0x7f97065096b0", "type":"CLASS", "class":"0x7f9709a1a6b0", "superclass":"0x7f9709a1a868", "real_class_name":"Object", "singleton":true, "references":["0x7f9709a1a868", "0x7f9706509728"], "memsize":480, "flags":{"wb_protected":true}}
[Bug #17519] set_visibility fails when a prepended module and a refinement both exist
以下のように refine
後のメソッドを特異クラスを経由して private
化しようとするとエラーになるというバグ報告です。
module Nothing; end class X # prepend しなかったらエラーにはならない prepend Nothing def hoge end end # これは OK X.new.singleton_class.class_eval { private :hoge } module NeverUsed refine X do def hoge(*keys) end end end # `private': undefined method `hoge' for class `#<Class:#<X:0x0000558fa95b7d70>>' (NameError) # Refinements で拡張したあとに呼ぶとエラーになる X.new.singleton_class.class_eval { private :hoge }
これは Ruby 3.0.1 で修正済みです。
[Bug #17488] Regression in Ruby 3: Hash#key? is non-deterministic when argument uses DelegateClass
次のように Ruby 3.0 で Hask#key?
に DelegateClass
を渡すと意図しない結果が返ってくるというバグ報告です。
puts "Running on Ruby: #{RUBY_DESCRIPTION}" program = <<~EOS require "delegate" TypeName = DelegateClass(String) hash = { "Int" => true, "Float" => true, "String" => true, "Boolean" => true, "WidgetFilter" => true, "WidgetAggregation" => true, "WidgetEdge" => true, "WidgetSortOrder" => true, "WidgetGrouping" => true, } puts hash.key?(TypeName.new("WidgetAggregation")) EOS iterations = 20 results = iterations.times.map { `ruby -e '#{program}'`.chomp }.tally # Ruby 3.0 で実行すると false が返ってくることがある puts "Results of checking `Hash#key?` #{iterations} times: #{results.inspect}" # Ruby 2.7 => Results of checking `Hash#key?` 20 times: {"true"=>20} # Ruby 3.0 => Results of checking `Hash#key?` 20 times: {"false"=>12, "true"=>8}
この問題は Ruby 3.0.1 で修正済みです。
[Bug #17481] Keyword arguments change value after calling super without arguments in Ruby 3.0
次のように super
を呼び出す前と後でキーワード引数の値が変わってしまうというバグ報告です。
class BaseTest def call(a:, b:, **) end end class Test < BaseTest def call(a:, b:, **options) p options # => {:c=>{}} super # super を呼び出した後で options の値が変わってしまっている… p options # => {:c=>{}, :a=>1, :b=>2} end end Test.new.call(a: 1, b: 2, c: {})
この問題は Ruby 3.1 で修正済みです。
これ、Ruby 3.0 系にバックポートしなくても大丈夫なんだろうか…。
おわりに
と、言うことで25日続けた 一人 bugs.ruby Advent Calendar 2021 もこれにて完走です。
最初はやろうかどうしようか迷っていたんですがなんだかんだ今年の Ruby を振り返る事ができて楽しかったです。
開発者の皆様、今年も1年間お疲れ様でした&ありがとうございました。