使用Ruby';s跟踪点

使用Ruby';s跟踪点,ruby,metaprogramming,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|

我能够使用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>'
“foo.sub(/(f)/){$1.upcase}
script.rb:20:在'method'中:类'Object'的未定义方法'sub'(NameError)
从script.rb:20:in`'
从script.rb:8:in'eval'
从script.rb:8:in'block in'
从script.rb:20:in`'
这看起来是因为使用C方法调用和常规Ruby方法调用时返回的绑定之间存在差异


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

正如您在问题中所指出的以及在中所记录的那样,
tp.self
返回一个跟踪对象,其中包含您正在寻找的
方法。
我认为你应该使用

method=tp.self.method(tp.method\u id)

而不是

method=eval(“method(:{tp.method\u id})”,tp.binding)

更新。关于你最后一段的解释<第一种情况下(当您调用
foo
时),code>tp.self
指向
main
,因为您在主上下文中定义了
foo
方法,而在第二种情况下,它指向
String
对象,因为
sub
在这里定义。但是
tp.binding.eval(“self”)
在这两种情况下都返回
main
,因为它返回调用上下文(不是您期望的“定义”上下文),并且在这两种情况下都是
main

更新(回复评论)我认为唯一的方法是使用monkey patch
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、
变量。正如所指出的,没有办法让它工作。但是,您仍然可以使用块参数(
s
,在我的示例中)。

感谢您的回复!但是,对于C参数和试图获取I get
[:trace]
tp.binding.local_变量时,sub只返回
[:rest]]
,看起来它来自主上下文。你知道如何解决这个问题吗?有趣的是,你应该提到这一点作为一种选择。这就是为什么我首先开始使用跟踪点来解决这个问题:。不幸的是,我一直在试图追踪
gsub
被调用的所有时间,人们肯定会使用$1和$2,而且它们不容易替换。我担心,如果你想更深入,你将不得不处理自己的C扩展…如果其他人想知道的话。我做了一些疯狂的工作来解析ruby源代码,因为我可以很容易地得到调用它的文件和行号。它不能100%地工作,但它比不记录任何C调用要好:在内存中保留一个正命中的缓存,这样我们就不必不断地在磁盘中读取。这是一个疯狂的黑客,但它主要是有效的。