使用Ruby的TracePoint获取方法参数

Sch*_*ems 5 ruby metaprogramming

我可以使用TracePoint API来访问Ruby方法的参数:

def foo(foo_arg)
end

trace = TracePoint.trace(:call, :c_call) do |tp|
  tp.disable
  case tp.method_id
  when :foo, :sub
    method = eval("method(:#{tp.method_id})", tp.binding)
    method.parameters.each do |p|
      puts "#{p.last}: #{tp.binding.local_variable_get(p.last)}"
    end
  end
  tp.enable
end

trace.enable

foo(10)
# => foo_arg: 10
Run Code Online (Sandbox Code Playgroud)

但是,当我使用ac方法调用尝试此操作时,出现错误。

"foo".sub(/(f)/) { $1.upcase }
script.rb:20:in `method': undefined method `sub' for class `Object' (NameError)
    from script.rb:20:in `<main>'
    from script.rb:8:in `eval'
    from script.rb:8:in `block in <main>'
    from script.rb:20:in `<main>'
Run Code Online (Sandbox Code Playgroud)

由于使用C方法调用和常规Ruby方法调用时返回的绑定之间存在差异,因此似乎发生这种情况。

在Ruby情况下tp.self等于tp.binding.eval("self")main然而在C的情况下tp.self"foo"tp.binding.eval("self")main。有没有一种方法可以将使用TracePoint的参数传递给Ruby和C定义的方法?

Але*_*ков 3

正如您在问题中指出的那样以及ruby​​ 文档中记录的那样,tp.self返回一个跟踪对象,该对象具有method您正在寻找的方法。我认为你应该使用

method = tp.self.method(tp.method_id)

代替

method = eval("method(:#{tp.method_id})", tp.binding)

更新。关于您有问题的最后一段的一些解释。tp.self在第一种情况下(当您调用时foo)是指向main,因为您在主上下文中定义了方法,而在第二种情况下foo它指向对象,因为在其中定义了。但在这两种情况下都会返回,因为它返回一个调用上下文(不是您期望的“定义”上下文),并且在这两种情况下它都是.Stringsubtp.binding.eval("self")mainmain

更新(回复评论)我认为做到这一点的唯一方法是猴子补丁sub和您感兴趣的所有其他方法。代码示例:

class String
  alias_method :old_sub, :sub
  def sub(*args, &block)
    old_sub(*args, &block)
  end
end

trace = TracePoint.trace(:call, :c_call) do |tp|
  tp.disable
  case tp.method_id
  when :sub
    method = tp.self.method(tp.method_id)
    puts method.parameters.inspect
  end
  tp.enable
end

trace.enable

"foo".sub(/(f)/) { |s| s.upcase }
Run Code Online (Sandbox Code Playgroud)

一个很大的缺点是你不能$1, $2, ...在原始块中使用变量。正如这里所指出的,没有办法让它发挥作用。但是您仍然可以使用块参数(s在我的示例中)。