🐡
[Bug #21125] method_missing が定義されている時に private メソッドが呼び出されてしまうバグ報告
[Bug #21125] Kernel is called first
- 以下のようなコードを実行した時に Ruby 3.3 と Ruby 3.4 で挙動に差異があるというバグ報告
class ObjectifiedHash
def initialize(hash)
@hash = hash
end
private
attr_reader :hash
def method_missing(method_name, *args, &block)
if hash.key?(method_name.to_sym)
hash[method_name.to_sym]
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
hash.key?(method_name.to_sym) || super
end
end
class Foo
def system(...)
# method_missing が呼び出されることを期待しているが
# Ruby 3.4 からは Kernel#system が呼び出されるようになった
raw.system(...)
end
def initialize(raw)
@raw = raw
end
def get_system
system
end
private
attr_reader :raw
end
class Test
def self.run
obj = ObjectifiedHash.new({
system: 'system'
})
foo = Foo.new(obj)
p foo.get_system
# Ruby 3.3 => "system"
# Ruby 3.4 => error: 'Kernel#system': wrong number of arguments (given 0, expected 1+) (ArgumentError)
end
end
Test.run
- ちょっとコードが長いんですがポイントとしては
Foo#system
内でraw.system(...)
を呼び出した時に Ruby 3.3 ではraw.method_missing
が呼び出されていたが Ruby 3.4 からはraw.system
が呼び出されるようになったという差異になります - これなんですが前提として『
private
メソッドを呼び出す場合は#method_missing
が呼び出される』ということが挙動として期待されます-
Kernel#system
はprivate
メソッドなので Ruby 3.3 の#method_missing
が呼びされることが期待される挙動になる
-
class X
def method_missing(...)
"method_missing"
end
def public_hoge
"public_hoge"
end
private
def private_hoge
"private_hoge"
end
end
def foo
x = X.new
# これは public メソッドが呼び出される
pp x.public_hoge # => "public_hoge"
# これは private メソッドを呼び出そうとしているので method_missing が呼ばれる
pp x.private_hoge # => "method_missing"
end
foo
- で、これなんですがどうやら転送引数(
...
) を利用している場合に Ruby 3.4 だとprivate
メソッドのほうが呼ばれてしまう、っていうバグみたいですね
class X
def method_missing(...)
"method_missing"
end
def public_hoge
"public_hoge"
end
private
def private_hoge
"private_hoge"
end
end
def foo(...)
x = X.new
# これは OK
# method_missing が呼ばれる
pp x.private_hoge # => "method_missing"
# method_missing ではなくて private メソッドが呼び出されてしまう
pp x.private_hoge(...) # => "private"
end
foo
- 最適化周りの問題なのかな?
Discussion