root/lang/ruby/bakagaikuonrails/vendor/rails/railties/lib/breakpoint.rb @ 7834

Revision 7834, 16.0 kB (checked in by dan5, 5 years ago)

imported bakagaikuonrails

Line 
1# The Breakpoint library provides the convenience of
2# being able to inspect and modify state, diagnose
3# bugs all via IRB by simply setting breakpoints in
4# your applications by the call of a method.
5#
6# This library was written and is supported by me,
7# Florian Gross. I can be reached at flgr@ccan.de
8# and enjoy getting feedback about my libraries.
9#
10# The whole library (including breakpoint_client.rb
11# and binding_of_caller.rb) is licensed under the
12# same license that Ruby uses. (Which is currently
13# either the GNU General Public License or a custom
14# one that allows for commercial usage.) If you for
15# some good reason need to use this under another
16# license please contact me.
17
18require 'irb'
19require 'binding_of_caller'
20require 'drb'
21require 'drb/acl'
22
23module Breakpoint
24  id = %q$Id: breakpoint.rb 92 2005-02-04 22:35:53Z flgr $
25  Version = id.split(" ")[2].to_i
26
27  extend self
28
29  # This will pop up an interactive ruby session at a
30  # pre-defined break point in a Ruby application. In
31  # this session you can examine the environment of
32  # the break point.
33  #
34  # You can get a list of variables in the context using
35  # local_variables via +local_variables+. You can then
36  # examine their values by typing their names.
37  #
38  # You can have a look at the call stack via +caller+.
39  #
40  # The source code around the location where the breakpoint
41  # was executed can be examined via +source_lines+. Its
42  # argument specifies how much lines of context to display.
43  # The default amount of context is 5 lines. Note that
44  # the call to +source_lines+ can raise an exception when
45  # it isn't able to read in the source code.
46  #
47  # breakpoints can also return a value. They will execute
48  # a supplied block for getting a default return value.
49  # A custom value can be returned from the session by doing
50  # +throw(:debug_return, value)+.
51  #
52  # You can also give names to break points which will be
53  # used in the message that is displayed upon execution
54  # of them.
55  #
56  # Here's a sample of how breakpoints should be placed:
57  #
58  #   class Person
59  #     def initialize(name, age)
60  #       @name, @age = name, age
61  #       breakpoint("Person#initialize")
62  #     end
63  #
64  #     attr_reader :age
65  #     def name
66  #       breakpoint("Person#name") { @name }
67  #     end
68  #   end
69  #
70  #   person = Person.new("Random Person", 23)
71  #   puts "Name: #{person.name}"
72  #
73  # And here is a sample debug session:
74  #
75  #   Executing break point "Person#initialize" at file.rb:4 in `initialize'
76  #   irb(#<Person:0x292fbe8>):001:0> local_variables
77  #   => ["name", "age", "_", "__"]
78  #   irb(#<Person:0x292fbe8>):002:0> [name, age]
79  #   => ["Random Person", 23]
80  #   irb(#<Person:0x292fbe8>):003:0> [@name, @age]
81  #   => ["Random Person", 23]
82  #   irb(#<Person:0x292fbe8>):004:0> self
83  #   => #<Person:0x292fbe8 @age=23, @name="Random Person">
84  #   irb(#<Person:0x292fbe8>):005:0> @age += 1; self
85  #   => #<Person:0x292fbe8 @age=24, @name="Random Person">
86  #   irb(#<Person:0x292fbe8>):006:0> exit
87  #   Executing break point "Person#name" at file.rb:9 in `name'
88  #   irb(#<Person:0x292fbe8>):001:0> throw(:debug_return, "Overriden name")
89  #   Name: Overriden name
90  #
91  # Breakpoint sessions will automatically have a few
92  # convenience methods available. See Breakpoint::CommandBundle
93  # for a list of them.
94  #
95  # Breakpoints can also be used remotely over sockets.
96  # This is implemented by running part of the IRB session
97  # in the application and part of it in a special client.
98  # You have to call Breakpoint.activate_drb to enable
99  # support for remote breakpoints and then run
100  # breakpoint_client.rb which is distributed with this
101  # library. See the documentation of Breakpoint.activate_drb
102  # for details.
103  def breakpoint(id = nil, context = nil, &block)
104    callstack = caller
105    callstack.slice!(0, 3) if callstack.first["breakpoint"]
106    file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
107
108    message = "Executing break point " + (id ? "#{id.inspect} " : "") +
109              "at #{file}:#{line}" + (method ? " in `#{method}'" : "")
110
111    if context then
112      return handle_breakpoint(context, message, file, line, &block)
113    end
114
115    Binding.of_caller do |binding_context|
116      handle_breakpoint(binding_context, message, file, line, &block)
117    end
118  end
119
120  module CommandBundle
121    # Proxy to a Breakpoint client. Lets you directly execute code
122    # in the context of the client.
123    class Client
124      def initialize(eval_handler) # :nodoc:
125        eval_handler.untaint
126        @eval_handler = eval_handler
127      end
128
129      instance_methods.each do |method|
130        next if method[/^__.+__$/]
131        undef_method method
132      end
133
134      # Executes the specified code at the client.
135      def eval(code)
136        @eval_handler.call(code)
137      end
138
139      # Will execute the specified statement at the client.
140      def method_missing(method, *args, &block)
141        if args.empty? and not block
142          result = eval "#{method}"
143        else
144          # This is a bit ugly. The alternative would be using an
145          # eval context instead of an eval handler for executing
146          # the code at the client. The problem with that approach
147          # is that we would have to handle special expressions
148          # like "self", "nil" or constants ourself which is hard.
149          remote = eval %{
150            result = lambda { |block, *args| #{method}(*args, &block) }
151            def result.call_with_block(*args, &block)
152              call(block, *args)
153            end
154            result
155          }
156          remote.call_with_block(*args, &block)
157        end
158
159        return result
160      end
161    end
162
163    # Returns the source code surrounding the location where the
164    # breakpoint was issued.
165    def source_lines(context = 5, return_line_numbers = false)
166      lines = File.readlines(@__bp_file).map { |line| line.chomp }
167
168      break_line = @__bp_line
169      start_line = [break_line - context, 1].max
170      end_line = break_line + context
171
172      result = lines[(start_line - 1) .. (end_line - 1)]
173
174      if return_line_numbers then
175        return [start_line, break_line, result]
176      else
177        return result
178      end
179    end
180
181    # Lets an object that will forward method calls to the breakpoint
182    # client. This is useful for outputting longer things at the client
183    # and so on. You can for example do these things:
184    #
185    #   client.puts "Hello" # outputs "Hello" at client console
186    #   # outputs "Hello" into the file temp.txt at the client
187    #   client.File.open("temp.txt", "w") { |f| f.puts "Hello" }
188    def client()
189      if Breakpoint.use_drb? then
190        sleep(0.5) until Breakpoint.drb_service.eval_handler
191        Client.new(Breakpoint.drb_service.eval_handler)
192      else
193        Client.new(lambda { |code| eval(code, TOPLEVEL_BINDING) })
194      end
195    end
196  end
197
198  def handle_breakpoint(context, message, file = "", line = "", &block) # :nodoc:
199    catch(:debug_return) do |value|
200      eval(%{
201        @__bp_file = #{file.inspect}
202        @__bp_line = #{line}
203        extend Breakpoint::CommandBundle
204        extend DRbUndumped if self
205      }, context) rescue nil
206
207      if not use_drb? then
208        puts message
209        IRB.start(nil, IRB::WorkSpace.new(context))
210      else
211        @drb_service.add_breakpoint(context, message)
212      end
213
214      block.call if block
215    end
216  end
217
218  # These exceptions will be raised on failed asserts
219  # if Breakpoint.asserts_cause_exceptions is set to
220  # true.
221  class FailedAssertError < RuntimeError
222  end
223
224  # This asserts that the block evaluates to true.
225  # If it doesn't evaluate to true a breakpoint will
226  # automatically be created at that execution point.
227  #
228  # You can disable assert checking in production
229  # code by setting Breakpoint.optimize_asserts to
230  # true. (It will still be enabled when Ruby is run
231  # via the -d argument.)
232  #
233  # Example:
234  #   person_name = "Foobar"
235  #   assert { not person_name.nil? }
236  #
237  # Note: If you want to use this method from an
238  # unit test, you will have to call it by its full
239  # name, Breakpoint.assert.
240  def assert(context = nil, &condition)
241    return if Breakpoint.optimize_asserts and not $DEBUG
242    return if yield
243
244    callstack = caller
245    callstack.slice!(0, 3) if callstack.first["assert"]
246    file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
247
248    message = "Assert failed at #{file}:#{line}#{" in `#{method}'" if method}."
249
250    if Breakpoint.asserts_cause_exceptions and not $DEBUG then
251      raise(Breakpoint::FailedAssertError, message)
252    end
253
254    message += " Executing implicit breakpoint."
255
256    if context then
257      return handle_breakpoint(context, message, file, line)
258    end
259
260    Binding.of_caller do |context|
261      handle_breakpoint(context, message, file, line)
262    end
263  end
264
265  # Whether asserts should be ignored if not in debug mode.
266  # Debug mode can be enabled by running ruby with the -d
267  # switch or by setting $DEBUG to true.
268  attr_accessor :optimize_asserts
269  self.optimize_asserts = false
270
271  # Whether an Exception should be raised on failed asserts
272  # in non-$DEBUG code or not. By default this is disabled.
273  attr_accessor :asserts_cause_exceptions
274  self.asserts_cause_exceptions = false
275  @use_drb = false
276
277  attr_reader :drb_service # :nodoc:
278
279  class DRbService # :nodoc:
280    include DRbUndumped
281
282    def initialize
283      @handler = @eval_handler = @collision_handler = nil
284
285      IRB.instance_eval { @CONF[:RC] = true }
286      IRB.run_config
287    end
288
289    def collision
290      sleep(0.5) until @collision_handler
291
292      @collision_handler.untaint
293
294      @collision_handler.call
295    end
296
297    def ping() end
298
299    def add_breakpoint(context, message)
300      workspace = IRB::WorkSpace.new(context)
301      workspace.extend(DRbUndumped)
302
303      sleep(0.5) until @handler
304
305      @handler.untaint
306      @handler.call(workspace, message)
307    end
308
309    attr_accessor :handler, :eval_handler, :collision_handler
310  end
311
312  # Will run Breakpoint in DRb mode. This will spawn a server
313  # that can be attached to via the breakpoint-client command
314  # whenever a breakpoint is executed. This is useful when you
315  # are debugging CGI applications or other applications where
316  # you can't access debug sessions via the standard input and
317  # output of your application.
318  #
319  # You can specify an URI where the DRb server will run at.
320  # This way you can specify the port the server runs on. The
321  # default URI is druby://localhost:42531.
322  #
323  # Please note that breakpoints will be skipped silently in
324  # case the DRb server can not spawned. (This can happen if
325  # the port is already used by another instance of your
326  # application on CGI or another application.)
327  #
328  # Also note that by default this will only allow access
329  # from localhost. You can however specify a list of
330  # allowed hosts or nil (to allow access from everywhere).
331  # But that will still not protect you from somebody
332  # reading the data as it goes through the net.
333  #
334  # A good approach for getting security and remote access
335  # is setting up an SSH tunnel between the DRb service
336  # and the client. This is usually done like this:
337  #
338  # $ ssh -L20000:127.0.0.1:20000 -R10000:127.0.0.1:10000 example.com
339  # (This will connect port 20000 at the client side to port
340  # 20000 at the server side, and port 10000 at the server
341  # side to port 10000 at the client side.)
342  #
343  # After that do this on the server side: (the code being debugged)
344  # Breakpoint.activate_drb("druby://127.0.0.1:20000", "localhost")
345  #
346  # And at the client side:
347  # ruby breakpoint_client.rb -c druby://127.0.0.1:10000 -s druby://127.0.0.1:20000
348  #
349  # Running through such a SSH proxy will also let you use
350  # breakpoint.rb in case you are behind a firewall.
351  #
352  # Detailed information about running DRb through firewalls is
353  # available at http://www.rubygarden.org/ruby?DrbTutorial
354  def activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'],
355    ignore_collisions = false)
356
357    return false if @use_drb
358
359    uri ||= 'druby://localhost:42531'
360
361    if allowed_hosts then
362      acl = ["deny", "all"]
363
364      Array(allowed_hosts).each do |host|
365        acl += ["allow", host]
366      end
367
368      DRb.install_acl(ACL.new(acl))
369    end
370
371    @use_drb = true
372    @drb_service = DRbService.new
373    did_collision = false
374    begin
375      @service = DRb.start_service(uri, @drb_service)
376    rescue Errno::EADDRINUSE
377      if ignore_collisions then
378        nil
379      else
380        # The port is already occupied by another
381        # Breakpoint service. We will try to tell
382        # the old service that we want its port.
383        # It will then forward that request to the
384        # user and retry.
385        unless did_collision then
386          DRbObject.new(nil, uri).collision
387          did_collision = true
388        end
389        sleep(10)
390        retry
391      end
392    end
393
394    return true
395  end
396
397  # Deactivates a running Breakpoint service.
398  def deactivate_drb
399    @service.stop_service unless @service.nil?
400    @service = nil
401    @use_drb = false
402    @drb_service = nil
403  end
404
405  # Returns true when Breakpoints are used over DRb.
406  # Breakpoint.activate_drb causes this to be true.
407  def use_drb?
408    @use_drb == true
409  end
410end
411
412module IRB # :nodoc:
413  class << self; remove_method :start; end
414  def self.start(ap_path = nil, main_context = nil, workspace = nil)
415    $0 = File::basename(ap_path, ".rb") if ap_path
416
417    # suppress some warnings about redefined constants
418    old_verbose, $VERBOSE = $VERBOSE, nil
419    IRB.setup(ap_path)
420    $VERBOSE = old_verbose
421
422    if @CONF[:SCRIPT] then
423      irb = Irb.new(main_context, @CONF[:SCRIPT])
424    else
425      irb = Irb.new(main_context)
426    end
427
428    if workspace then
429      irb.context.workspace = workspace
430    end
431
432    @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
433    @CONF[:MAIN_CONTEXT] = irb.context
434
435    old_sigint = trap("SIGINT") do
436      begin
437        irb.signal_handle
438      rescue RubyLex::TerminateLineInput
439        # ignored
440      end
441    end
442   
443    catch(:IRB_EXIT) do
444      irb.eval_input
445    end
446  ensure
447    trap("SIGINT", old_sigint)
448  end
449
450  class << self
451    alias :old_CurrentContext :CurrentContext
452    remove_method :CurrentContext
453  end
454  def IRB.CurrentContext
455    if old_CurrentContext.nil? and Breakpoint.use_drb? then
456      result = Object.new
457      def result.last_value; end
458      return result
459    else
460      old_CurrentContext
461    end
462  end
463  def IRB.parse_opts() end
464
465  class Context #:nodoc:
466    alias :old_evaluate :evaluate
467    def evaluate(line, line_no)
468      if line.chomp == "exit" then
469        exit
470      else
471        old_evaluate(line, line_no)
472      end
473    end
474  end
475
476  class WorkSpace #:nodoc:
477    alias :old_evaluate :evaluate
478
479    def evaluate(*args)
480      if Breakpoint.use_drb? then
481        result = old_evaluate(*args)
482        if args[0] != :no_proxy and
483          not [true, false, nil].include?(result)
484        then
485          result.extend(DRbUndumped) rescue nil
486        end
487        return result
488      else
489        old_evaluate(*args)
490      end
491    end
492  end
493
494  module InputCompletor #:nodoc:
495    def self.eval(code, context, *more)
496      # Big hack, this assumes that InputCompletor
497      # will only call eval() when it wants code
498      # to be executed in the IRB context.
499      IRB.conf[:MAIN_CONTEXT].workspace.evaluate(:no_proxy, code, *more)
500    end
501  end
502end
503
504module DRb #:nodoc:
505  class DRbObject #:nodoc:
506    undef :inspect if method_defined?(:inspect)
507    undef :clone if method_defined?(:clone)
508  end
509end
510
511# See Breakpoint.breakpoint
512def breakpoint(id = nil, &block)
513  Binding.of_caller do |context|
514    Breakpoint.breakpoint(id, context, &block)
515  end
516end
517
518# See Breakpoint.assert
519def assert(&block)
520  Binding.of_caller do |context|
521    Breakpoint.assert(context, &block)
522  end
523end
Note: See TracBrowser for help on using the browser.