glue 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,523 @@
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
+
18
+ require 'irb'
19
+ require 'binding_of_caller'
20
+ require 'drb'
21
+ require 'drb/acl'
22
+
23
+ module 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
410
+ end
411
+
412
+ module 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
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
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
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
502
+ end
503
+
504
+ module DRb # :nodoc:
505
+ class DRbObject
506
+ undef :inspect if method_defined?(:inspect)
507
+ undef :clone if method_defined?(:clone)
508
+ end
509
+ end
510
+
511
+ # See Breakpoint.breakpoint
512
+ def breakpoint(id = nil, &block)
513
+ Binding.of_caller do |context|
514
+ Breakpoint.breakpoint(id, context, &block)
515
+ end
516
+ end
517
+
518
+ # See Breakpoint.assert
519
+ def assert(&block)
520
+ Binding.of_caller do |context|
521
+ Breakpoint.assert(context, &block)
522
+ end
523
+ end