使用Ruby的TracePoint获取方法参数

时间:2015-06-01 22:36:59

标签: 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

然而,当我尝试使用c方法调用时,我收到错误。

"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>'

这看起来好像是因为使用C方法调用时返回的绑定与常规Ruby方法调用之间的差异。

在Ruby案例tp.self等于tp.binding.eval("self")main但是在C案例中tp.self"foo"tp.binding.eval("self")是{ {1}}。有没有办法将参数传递给使用TracePoint的方法用于Ruby和C定义的方法?

1 个答案:

答案 0 :(得分:2)

当您在问题中指出并且在ruby documentation中记录时,tp.self会返回一个跟踪对象,该对象具有您正在寻找的method方法。 我认为你应该使用

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

而不是

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

<强>更新即可。关于你最后一个段落的一些解释。 tp.self在第一种情况下(当您致电foo时)指向main,因为您在主要上下文中定义了foo方法,并且它指向String个对象第二种情况,因为那里定义了sub。但是tp.binding.eval("self")在两种情况下都会返回main,因为它会返回一个调用上下文(而不是您期望的&#39;定义&#39;上下文),并且在两种情况下都是main。< / p>

更新(回复评论)我认为这样做的唯一方法是修补补丁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 }

一个很大的缺点是您无法在原始块中使用$1, $2, ...变量。正如所指出的here,无法使其发挥作用。但是,您仍然可以使用块参数(在我的示例中为s)。