2022/02/10 今回の気になった bugs.ruby のチケット

今週はエンコーディングASCII_8BIT という名前を BINARY に変える提案がありました。

[Feature #18576] Rename ASCII-8BIT encoding to BINARY

  • エンコーディングASCII-8BITBINARY という名前に変更する提案
  • 以下のようなエラーメッセージで ASCII-8BIT と表示されても分かりづらいらしい
    • Ruby だと ASCII-8BITno encodingbinary という意味合いで使われているが Ruby をよく知らないと意味がわかりづらい
>> "fée" + "\xFF".b
(irb):3:in `+': incompatible character encodings: UTF-8 and ASCII-8BIT (Encoding::CompatibilityError)
  • 現状は BINARYASCII_8BITエイリアスとして存在しているんですがそれを逆にする感じなんですかね
pp Encoding::ASCII_8BIT  # => #<Encoding:ASCII-8BIT>
pp Encoding::BINARY      # => #<Encoding:ASCII-8BIT>

[Bug #18575] [BUG] unsupported: T_NONE

  • 巨大な CSV ファイルを処理している時にクラッシュするというバグ報告
  • 以下のコードで再現するらしい
require 'csv'
parsed = CSV.parse("Foo,bAr,baZ\nfoo,bar,baz", headers: true)

while true
  parsed.map do |row|
    obj = row.to_h
    obj.transform_keys! { |k| k.strip.downcase }
  end
end

[Feature #18573] Object#pack1

  • Array#pack を使用する時に配列の要素が1つのケースがよくある
    • 標準ライブラリなどでもあるらしい
[codepoint].pack('U')
[digest].pack('m0')
[mail_body].pack('M')
[ip_address].pack('N')
  • このような際にわざわざ Array にするのが手間なので Object#pack1 というメソッドを追加する提案
codepoint.pack('U')
digest.pack('m0')
mail_body.pack('M')
ip_address.pack('N')
  • Object に生やすのではなくて String.pack1(format, arg) を追加するのはどうか、みたいなコメントとかもあります
  • ちなみに Array#pack は指定されたテンプレートに沿って文字列に変換するメソッド
# コードポイントから UTF-8 の文字列に変換する
pp [12354].pack("U*")
# => "あ"

[Bug #18572] Performance regression when invoking refined methods

  • Ruby 2.7 と比較して Refinement で定義したメソッドの呼び出しが遅くなっているというバグ報告
    • using していなくても遅くなっている
  • 以下、検証結果
require "benchmark_driver"

source = <<~RUBY
class Foo
  def original
  end
  def refined
  end
end
module FooRefinements
  refine Foo do
    def refined
      raise "never called"
    end
  end
end
FOO = Foo.new
RUBY

Benchmark.driver do |x|
  x.prelude %Q{
    #{source}
  }
  x.report "no-op original", %{ FOO.original }
  x.report "no-op refined", %{ FOO.refined }
end
...
Comparison:
      no-op original:  54831732.8 i/s
       no-op refined:  28231384.4 i/s - 1.94x  slower
...
Comparison:
       no-op refined:  57847396.8 i/s
      no-op original:  56289619.5 i/s - 1.03x  slower

[Bug #18569] RubyVM::InstructionSequence#disasm returns nil for composed functions

  • Proc#>> の結果を RubyVM::InstructionSequence#disasm に渡すと nil が返ってくるというバグ報告
# Proc オブジェクト渡すとそのブロックの命令が返ってくる
first_proc = proc { |x| x + 2 }
puts RubyVM::InstructionSequence.disasm(first_proc)
# =>
# == disasm: #<ISeq:block in <main>@disasm.rb:3 (3,18)-(3,31)> (catch: FALSE)
# local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
# [ 1] x@0<Arg>
# 0000 getlocal_WC_0                          x@0                       (   3)[LiBc]
# 0002 putobject                              2
# 0004 opt_plus                               <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
# 0006 leave                                  [Br]
# -------------------------------------

# Proc#>> の結果を .disasm に渡すと nil が返ってくる
pp RubyVM::InstructionSequence.disasm(first_proc >> first_proc)
# => nil
  • これは Proc#>> の結果が命令シーケンスを持っていないからが理由とのこと
  • 命令シーケンスが必要な場合は自分で Proc オブジェクトを生成する必要がある
first_proc = proc { |x| x + 2 }
# これだと OK
puts RubyVM::InstructionSequence.disasm(proc { |x| first_proc.call(x) + 2 })
# =>
# == disasm: #<ISeq:block in <main>@disasm.rb:3 (3,18)-(3,31)> (catch: FALSE)
# local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
# [ 1] x@0<Arg>
# 0000 getlocal_WC_1                          first_proc@0              (   2)[LiBc]
# 0002 getlocal_WC_0                          x@0
# 0004 opt_send_without_block                 <calldata!mid:call, argc:1, ARGS_SIMPLE>
# 0006 putobject                              2
# 0008 opt_plus                               <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
# 0010 leave                                  [Br]

[Bug #18578] Hash#shift を繰り返していると ruby が無応答になる。

  • 以下のように Hash#shift を繰り返し呼び出すと Ruby の実行が止まってしまうことがるというバグ報告
H = {}
100.times{|n|
    while H.size < n
        k = Random.rand 0..1<<30
        H[k] = 1 # たぶんここで止まる。  
    end
    warn "size: #{H.size} before shifting."
    0 while H.shift
    warn "empty?: #{H.empty?}"
}
warn :exit
  • このバグは Ruby 2.5 から発生している
  • Hash が空になった後に Hash#shift を呼び出すと意図しない挙動になっていたぽい
  • このバグは既に修正済み