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

今週は CRuby を WASI に対応する変更がマージされました。

[Feature #18462] Proposal to merge WASI based WebAssembly support

[Bug #17545] Calling dup on a subclass of Proc returns a Proc and not the subclass

  • Proc を継承したクラスで dup を呼ぶとサブクラスではなくて Proc オブジェクトが返ってくるバグ報告
class MyProc < Proc
end

p MyProc.new {}.dup
# => #<Proc:0x00007f2a6fadd8b0 /tmp/v9WO1yb/17:4>
  • これは Ruby 1.9 の時に意図的に変更されていたらしい
  • Ruby 3.2 からはサブクラスのオブジェクトが返ってくるように修正された
  • ちなみに回避方法として以下のように対応する事ができる
class MyProc < Proc
  def dup
    self.class.new(&super)
  end
end

p MyProc.new {}.dup
# => #<MyProc:0x00007f0bf0b95368 /tmp/v9WO1yb/18:7>

[Bug #18487] Kernel#binding behaves differently depending on implementation language of items on the stack

  • 現在のスタックフレームでない binding オブジェクトを取得することができるというバグ報告
  • 以下のようなコードでメソッドの呼び出し元の binding オブジェクトを取得する事ができる
class Magic
  define_singleton_method :modify_caller_env!, method(:binding).to_proc >> ->(bndg) {
    # 呼び出し元の binding オブジェクトに対してローカル変数を設定する
    bndg.local_variable_set(:my_var, 42)
  }
end

my_var = 1

Magic.modify_caller_env!

p my_var #=> 42
  • なるほど?
  • Proc#>> あたりで関数合成しているのがポイントなんですかね?

[Feature #18461] closures are capturing unused variables

  • Ruby では使用されていないローカル変数もキャプチャしている
def foo
  a = 1
  ->{}
end

# メソッド内で a は使用されていないが a という変数をキャプチャしている
p foo.binding.local_variables # [:a]
  • これを最適化しようという提案
    • JavaScript エンジンの V8 だと同様の最適化をしているらしい
  • しかし、Ruby の場合は binding を経由して外から変数を参照できたりする
def foo
  a = 1
  [->{}, ->{}]
end
x, y = foo
x.binding.local_variable_get(:a) # => 1
y.binding.local_variable_get(:a) # => 1
x.binding.local_variable_set(:a, 2)
x.binding.local_variable_get(:a) # => 2
y.binding.local_variable_get(:a) # => 2
  • 議論は盛り上がっているんですが今は結論が出ないまま止まっちゃってるぽいですね
  • 最適化を行うと非互換になってしまうんですが、仮に最適化した場合どれぐらいパフォーマンスが向上するんですかね?
    • 普段コードを書いている時に基本的に無駄な変数は定義しないようにしているのでそんなに効果があるかちょっと懐疑的
  • 実行時に最適化するんじゃなくて Warning 出たりするぐらいで十分なんじゃないかなあ

[Feature #18438] Add Exception#additional_message to show additional error information

  • 現状 did_you_meanerror_highlight などは Exception#message を書き換えてエラーメッセージを変更している
begin; 1.time; rescue NoMethodError; pp $!.message; end
#=> "undefined method `time' for 1:Integer\n" +
#   "\n" +
#   "  1.time\n" +
#   "   ^^^^^\n" +
#   "Did you mean?  times"
  • これには問題があって Exception#message を変更するので Exception#message の戻り値をチェックするテストを中断してしまう
  • この問題を対処するために Exception#additional_message という新しいメソッドを追加する提案
class MyError < StandardError
  def message = "my error!"
  # エラーメッセージに追加するテキストを定義する
  def additional_message = "This is\nan additional\nmessage"
end

raise MyError
$ ./miniruby test.rb
test.rb:6:in `<main>': my error! (MyError)
| This is
| an additional
| message
def message
  @message
end

def description(highlight: false)
  "#{message}\nSome extra info"
end

def full_message(highlight: false, order: ...)
  "#{description(highlight: highlight)}\n#{backtrace...}"
end

[Bug #15928] Constant declaration does not conform to JIS 3017:2013

  • 次のように定数を代入する時に rhs -> expr の順で評価が行われる
expr::C = rhs
  • しかし、これは JIS 3017:2013 に準拠していないというバグ報告
  • 2年以上前のチケットだったが最近修正された
  • 最新版では expr -> rhs の順番で評価されるようになる
def expr
  print "expr -> "
  Object
end

def rhs
  print "rhs -> "
end

expr::Hoge = rhs
# Ruby 3.1 => rhs -> expr ->
# Ruby 3.2 => expr -> rhs ->

[Bug #18435] Calling protected on ancestor method changes result of instance_methods(false)

  • instance_methods(false) はレシーバのクラスオブジェクトで定義されているメソッド名のみを返す
module A
  def method1()  end
end

class B
  include A

  def method2()  end
end

p B.instance_methods(false) #=> [:method2]
  • しかし、親クラスのメソッドを protected すると instance_methods(false) にも含まれてしまうというバグ報告
    • ちなみに親クラスの private メソッドをサブクラスで public にすると同様の挙動になる
module A
  private def method1()  end
end

class B
  include A

  public :method1

  def method2()  end
end

p B.instance_methods(false) #=> [:method2, :method1]
p B.instance_method(:method1).owner #=> A
  • この問題は最新版だと修正済み