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?
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With