Get Method Arguments using Ruby's TracePoint

主宰稳场 提交于 2020-01-01 10:12:12

问题


I'm able to get access to a Ruby method's arguments using the TracePoint API:

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

However when I try this with a c method call, I get an error.

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

This looks like it happens because of a discrepancy between the binding returned when using a C method call and regular Ruby method call.

In the Ruby case tp.self is equal to tp.binding.eval("self") is main however in the C case tp.self is "foo" and tp.binding.eval("self") is main. Is there a way to get the arguments passed into a method using TracePoint for both Ruby and C defined methods?


回答1:


As you point in your question and as it documented in ruby documentation, tp.self returns a traced object, which have a method method you are looking for. I think you should use

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

instead of

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

UPDATE. Some explanation regarding your last paragraph in question. tp.self in first case (when you call foo) is point to main, because you define foo method in main context and it points to String object in second case because sub is defined there. But tp.binding.eval("self") returns main in both cases because it returns a calling context (not a 'define' context as you expect) and in both cases it is main.

UPDATE (in reply to comment) I think that the only way to do this is to monkey patch sub and all other methods that you are interesting for. Code example:

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 }

One big drawback is that you can't use $1, $2, ... vars in your original blocks. As pointed here where is no way to make it works. However you can still use block parameters (s in my example).



来源:https://stackoverflow.com/questions/30584454/get-method-arguments-using-rubys-tracepoint

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!