📘

[Bug #21125] 意図せずに Kernel のメソッドが優先して呼び出されてしまうバグ報告

に公開

[Bug #21125] Kernel is called first

  • 次のコードが 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
end

class Foo
  def system(...)
    # (...) がない場合は ObjectifiedHash#system が呼ばれる
    # raw.system

    # (...) がある場合は Kernel#system が呼ばれエラーになる
    # error: wrong number of arguments (given 0, expected 1+) (ArgumentError)
    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)

    foo.get_system
  end
end

Test.run
  • ちょっと分かりづらいんですが ObjectifiedHash#method_missing 経由で #system メソッドを呼び出そうとした場合に Kernel#system のほうが呼び出されてしまい、意図する挙動にならなくなっています
  • またこれは転送引数を渡したときに再現するみたいですね
  • これなんですがもう少し簡略化すると以下でも再現します
class ObjectifiedHash
  def method_missing(method_name, *args, &block)
    "ObjectifiedHash#method_missing"
  end

  private

  def system
    "ObjectifiedHash#system"
  end
end

def test(...)
  # 本来であれば private メソッドをレシーバ付きで呼び出したときは #method_missing が優先して呼び出される
  # しかし、転送引数を渡した場合は意図せずに private メソッドが呼びされてしまう
  ObjectifiedHash.new.system(...)
end

pp test
# => "ObjectifiedHash#system"
GitHubで編集を提案

Discussion