【问题标题】:Get Method Arguments using Ruby's TracePoint使用 Ruby 的 TracePoint 获取方法参数
【发布时间】:2015-06-01 22:36:59
【问题描述】:

我可以使用 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")main。对于 Ruby 和 C 定义的方法,有没有办法使用 TracePoint 将参数传递给方法?

【问题讨论】:

    标签: ruby metaprogramming


    【解决方案1】:

    正如您在问题中指出的那样,正如 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,因为它返回一个调用上下文(而不是您期望的“定义”上下文),并且在这两种情况下都是main

    更新(回复评论)我认为唯一的方法是猴子补丁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)。

    【讨论】:

    • 感谢您的回复!这行得通,但是 sub 只为 C 参数返回 [[:rest]],当我尝试为我获取 tp.binding.local_variables 时,我得到了 [:trace],它看起来像是来自主上下文。你知道如何解决这个问题吗?
    • 有趣的是,您应该将其作为一个选项提及。这就是为什么我首先开始使用 TracePoint 来解决这个问题:stackoverflow.com/questions/30512945/…。不幸的是,我一直在尝试追踪 gsub 被调用的所有时间,人们肯定会使用 $1 和 $2 并且它们不容易替换。
    • 恐怕如果你想更深入,你将不得不处理自己的 C 扩展......
    • 如果有人想知道。我做了一些疯狂的 sh*t 来解析 ruby​​ 源代码,因为我可以轻松获取调用它的文件和行号。它不能 100% 工作,但总比不记录任何 C 调用要好:github.com/schneems/let_it_go/blob/… 在内存中保留了积极命中的缓存,因此我们不必不断地读取磁盘。这是一个疯狂的 hack,但它主要是有效的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-07-25
    • 1970-01-01
    • 2012-01-02
    • 1970-01-01
    • 2011-03-24
    • 1970-01-01
    • 2017-07-19
    相关资源
    最近更新 更多