eigenclass logo
MAIN  Index  Search  Changes  PageRank  Login

Binding.of_caller and breakpoint breaking in 4 days (Ruby 1.8.5)

You might have heard of Florian Groß' Binding.of_caller before. More probably, you might have used his breakpoint library (yes, the one included in Rails, which can be used with script/breakpointer). If you read ruby-core a few months ago, you'll know that it only worked thanks to a bug which is being fixed in Ruby 1.8.5. Which means that breakpoint will be breaking in under a week time, since matz is releasing 1.8.5 next Friday or Saturday at the latest*1.

Fortunately, I'd kept an unreleased rcov branch with some 500 lines of C to implement a better Kernel#caller method, which I had discarded at first because there was a much easier way to proceed when bindings were not needed.

So, I have the functionality that will keep breakpoint alive after the arrival of 1.8.5, but I need some help to refine the interface before it can be released; better get the naming right the first time, and this is something several brains tend to do better than one.

This is what it looks like currently:

require 'binding_n'
def a; _a = 1; b end
def b; _b = 2; c end
def c; _c = 3; d end
def d; _d = 4; send(:e) end
def e
  levels = binding_n(10)
  levels.each do |klass, id, file, line, binding, lang|
    puts "=" * 80
    p [klass, id, file, line, lang]
    next unless binding
    p eval("local_variables", binding).map{|x| [x, eval(x, binding)]}
  end
end

def x
  Kernel.install_binding_n_hook
  a
  Kernel.remove_binding_n_hook
end

x

$ ruby test.rb 
================================================================================
[Object, :e, "test.rb", 8, :Ruby]
[["levels", 
 [[Object, :e, "test.rb", 8, #<Binding:0xa7ddaac0>, :Ruby], 
  [Kernel, :send, nil, nil, nil, :C], 
  [Object, :d, "test.rb", 7, #<Binding:0xa7ddaae8>, :Ruby], 
  [Object, :c, "test.rb", 6, #<Binding:0xa7ddab10>, :Ruby], 
  [Object, :b, "test.rb", 5, #<Binding:0xa7ddab38>, :Ruby], 
  [Object, :a, "test.rb", 4, #<Binding:0xa7ddab60>, :Ruby], 
  [:unknown, :x, "test.rb", 18, nil, nil], 
  [:unknown, :unknown, "test.rb", 23, nil, nil]]]]
================================================================================
[Kernel, :send, nil, nil, :C]
================================================================================
[Object, :d, "test.rb", 7, :Ruby]
[["_d", 4]]
================================================================================
[Object, :c, "test.rb", 6, :Ruby]
[["_c", 3]]
================================================================================
[Object, :b, "test.rb", 5, :Ruby]
[["_b", 2]]
================================================================================
[Object, :a, "test.rb", 4, :Ruby]
[["_a", 1]]
================================================================================
[:unknown, :x, "test.rb", 18, nil]
================================================================================
[:unknown, :unknown, "test.rb", 23, nil]

Open questions:

  • binding_n ? Maybe call_stack would be better.
  • is it OK to add that method directly to Kernel? (tentative answer: yup, similar to caller)
  • what about the methods to enable/disable the call stack recording?

This is hopefully a temporary solution: it'd be possible to implement #binding_n without event_hooks (and hence with no speed penalty if not used) if some variables in eval.c were made public (i.e. not static), the same way _why got some extern'ed for his sandbox extension. However, it's too late to have that changed in Ruby 1.8.5 (we're only a few days away from the release), so this will do OK until the next stable release.


No Title - Kent (2006-08-25 (Fri) 12:12:05)

Have you considered using ruby-debug for this:

require "rubygems"
require 'ruby-debug'
Debugger.start

module Kernel
  def binding_n(n = 0)
    frame = Debugger.current_context.frames[n+1]
    frame.binding if frame
  end
end

def test
  puts eval("var", binding_n(1))
end

var = 'Hello'
test

mfp 2006-09-07 (Thr) 04:04:47

I've taken a look at ruby-debug (it looks very nice btw.) and indeed it's a superset of my hack. It seems it's going to be a bit slower (haven't timed it yet) because it does more things in the event handler, though. I'm releasing what I have so far so people can keep using script/breakpointer. If it turns out that there's no difference in speed I'll be happy to see it replaced by ruby-debug :-)


names - trans (2006-08-22 (Tue) 22:18:37)

Well there are a good many choices. I think #call_stack is the most noted. Other options might use the word 'frames' or perhaps better would be 'trace' or 'tracepoints'.

Definitely add the method to Kernel unless perhpas a separate module can work: Call.frames or Trace.points. If something like that can be done it could be very useful becuase you could have a varitey of methods for accessing the informaton in differnt ways.

The hooks remind me of open/close scenario so maybe open_tracing and close_tracing. Or better like on/off: trace_on and trace_off, or what have you. Again, if the module can be used: Trace.open or Trace.on.

HTH.

evan 2006-08-22 (Tue) 22:31:05

Kernel.stack or Kernel.call_stack gets my vote

mfp 2006-08-23 (Wed) 09:38:12

Thanks to you both, I think I'll define Kernel#call_stack and #call_stack_trace_on/off for the time being. Further methods would fall in a separate namespace, if needed.

Anonymous 2006-08-23 (Wed) 10:29:53

Cool, but maybe a little long winded? Perhaps:

 stack_trace
 stack_trace_on
 stack_trace_off

-or-

 call_stack
 call_stack_on
 call_stack_off

So this is making it into 1.8.5!?

mfp 2006-08-23 (Wed) 16:37:53

call_stack_on/off sounds nice.

So this is making it into 1.8.5!?

1.8.5 is frozen; otherwise, we'd just ask matz to "extern" some variables instead of using an event_hook. There have been many requests to add something like #call_stack to the core in the past, but they got stuck, like many RCRs. [Instead of demanding that this be added to Ruby, it'd be easier to ask for some variables to be exported so it can be implemented in third-party extensions, and I'm thinking of doing this after 1.8.5]

What I've written is but a workaround to have a Binding.of_caller that can work with 1.8.5, so that people can still use Florian Groß' breakpoint (pretty popular nowadays since it's included in Rails). I'll be releasing it in a few days.

Matt Todd 2006-08-27 (Sun) 13:44:45

I'm in favor of #tracepoints.


*1 it's been broken for a while if you were using the previews snapshots or the CVS version, of course