🐡
[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