DebugClients/Ruby/DebugClientBaseModule.rb

changeset 4553
a6b2acd1a355
parent 4552
b1ea4ea0190e
child 4554
f3428ddd577c
equal deleted inserted replaced
4552:b1ea4ea0190e 4553:a6b2acd1a355
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2005 - 2015 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 =begin edoc
7 File implementing a debug client base module.
8 =end
9
10 require 'socket'
11
12 require 'DebugQuit'
13 require 'DebugProtocol'
14 require 'DebugClientCapabilities'
15 require 'AsyncFile'
16 require 'Config'
17 require 'Completer'
18
19 $DebugClientInstance = nil
20 $debugging = false
21
22 module DebugClientBase
23 =begin edoc
24 Module implementing the client side of the debugger.
25
26 It provides access to the Ruby interpeter from a debugger running in another
27 process.
28
29 The protocol between the debugger and the client assumes that there will be
30 a single source of debugger commands and a single source of Ruby
31 statements. Commands and statement are always exactly one line and may be
32 interspersed.
33
34 The protocol is as follows. First the client opens a connection to the
35 debugger and then sends a series of one line commands. A command is either
36 &gt;Load&lt;, &gt;Step&lt;, &gt;StepInto&lt;, ... or a Ruby statement.
37 See DebugProtocol.rb for a listing of valid protocol tokens.
38
39 A Ruby statement consists of the statement to execute, followed (in a
40 separate line) by &gt;OK?&lt;. If the statement was incomplete then the response
41 is &gt;Continue&lt;. If there was an exception then the response is &gt;Exception&lt;.
42 Otherwise the response is &gt;OK&lt;. The reason for the &gt;OK?&lt; part is to
43 provide a sentinal (ie. the responding &gt;OK&lt;) after any possible output as a
44 result of executing the command.
45
46 The client may send any other lines at any other time which should be
47 interpreted as program output.
48
49 If the debugger closes the session there is no response from the client.
50 The client may close the session at any time as a result of the script
51 being debugged closing or crashing.
52
53 <b>Note</b>: This module is meant to be mixed in by individual DebugClient classes.
54 Do not use it directly.
55 =end
56 @@clientCapabilities = HasDebugger | HasInterpreter | HasShell | HasCompleter
57
58 attr_accessor :passive, :traceRuby
59
60 def initializeDebugClient
61 =begin edoc
62 Method to initialize the module
63 =end
64
65 # The context to run the debugged program in.
66 @debugBinding = eval("def e4dc_DebugBinding; binding; end; e4dc_DebugBinding",
67 TOPLEVEL_BINDING,
68 __FILE__,
69 __LINE__ - 3)
70
71 # The context to run the shell commands in.
72 @shellBinding = eval("def e4dc_ShellBinding; binding; end; e4dc_ShellBinding",
73 TOPLEVEL_BINDING,
74 __FILE__,
75 __LINE__ - 3)
76
77 # stack frames
78 @frames = []
79 @framenr = 0
80
81 # The list of complete lines to execute.
82 @buffer = ''
83 @lineno = 0
84
85 # The list of regexp objects to filter variables against
86 @globalsFilterObjects = []
87 @localsFilterObjects = []
88
89 @pendingResponse = ResponseOK
90 @mainProcStr = nil # used for the passive mode
91 @passive = false # used to indicate the passive mode
92 @running = nil
93
94 @readstream = nil
95 @writestream = nil
96 @errorstream = nil
97
98 @variant = 'You should not see this'
99
100 @completer = Completer.new(@shellBinding)
101 end
102
103 def handleException
104 =begin edoc
105 Private method called in the case of an exception
106
107 It ensures that the debug server is informed of the raised exception.
108 =end
109 @pendingResponse = ResponseException
110 end
111
112 def sessionClose
113 =begin edoc
114 Privat method to close the session with the debugger and terminate.
115 =end
116 set_trace_func nil
117 if $debugging
118 $debugging = false
119 @running = nil
120 DEBUGGER__.context(DEBUGGER__.last_thread).step_quit()
121 end
122
123 # clean up asyncio
124 disconnect()
125
126 # make sure we close down our end of the socket
127 # might be overkill as normally stdin, stdout and stderr
128 # SHOULD be closed on exit, but it does not hurt to do it here
129 @readstream.close()
130 @writestream.close()
131 @errorstream.close()
132
133 # Ok, go away.
134 exit()
135 end
136
137 def unhandled_exception(exc)
138 =begin edoc
139 Private method to report an unhandled exception.
140
141 @param exc the exception object
142 =end
143 if SystemExit === exc
144 if $debugging
145 $debugging = false
146 else
147 progTerminated(exc.status)
148 end
149 return
150 end
151
152 # split the exception message
153 msgParts = exc.message.split(":", 3)
154 filename = File.expand_path(msgParts[0])
155 linenr = msgParts[1].to_i
156
157 if ScriptError === exc
158 msgParts = msgParts[2].split(":", 3)
159 filename = msgParts[0].sub(/in `require'/, "")
160 linenr = msgParts[1].to_i
161 exclist = [""]
162 exclist << [filename, linenr, 0]
163 write("%s%s\n" % [ResponseSyntax, exclist.inspect])
164 return
165 end
166
167 exclist = ["unhandled %s" % exc.class, msgParts[2].sub(/in /, "")]
168 exclist << [filename, linenr]
169
170 # process the traceback
171 frList = exc.backtrace
172 frList.each do |frame|
173 next if frame =~ /DebugClientBaseModule/
174 break if frame =~ /\(eval\)/
175 frameParts = frame.split(":", 3)
176 filename = File.expand_path(frameParts[0])
177 linenr = frameParts[1].to_i
178 next if [filename, linenr] == exclist[-1]
179 exclist << [filename, linenr]
180 end
181
182 write("%s%s\n" % [ResponseException, exclist.inspect])
183 end
184
185 def handleLine(line)
186 =begin edoc
187 Private method to handle the receipt of a complete line.
188
189 It first looks for a valid protocol token at the start of the line. Thereafter
190 it trys to execute the lines accumulated so far.
191
192 @param line the received line
193 =end
194
195 # Remove any newline
196 if line[-1] == "\n"
197 line = line[0...-1]
198 end
199
200 ## STDOUT << line << "\n" ## debug
201
202 eoc = line.index("<")
203
204 if eoc and eoc >= 0 and line[0,1] == ">"
205 # Get the command part and any argument
206 cmd = line[0..eoc]
207 arg = line[eoc+1..-1]
208
209 case cmd
210 when RequestOK
211 write(@pendingResponse + "\n")
212 @pendingResponse = ResponseOK
213 return
214
215 when RequestEnv
216 # convert a Python stringified hash to a Ruby stringified hash
217 arg.gsub!(/: u/, "=>")
218 arg.gsub!(/u'/, "'")
219 eval(arg).each do |key, value|
220 if key[-1..-1] == "+"
221 key = key[0..-2]
222 if ENV[key]
223 ENV[key] += value
224 else
225 ENV[key] = value
226 end
227 else
228 ENV[key] = value
229 end
230 end
231 return
232
233 when RequestVariables
234 frmnr, scope, filter = eval("[%s]" % arg.gsub(/u'/, "'").gsub(/u"/,'"'))
235 dumpVariables(frmnr.to_i, scope.to_i, filter)
236 return
237
238 when RequestVariable
239 var, frmnr, scope, filter = \
240 eval("[%s]" % arg.gsub(/u'/, "'").gsub(/u"/,'"'))
241 dumpVariable(var, frmnr.to_i, scope.to_i, filter)
242 return
243
244 when RequestStep
245 DEBUGGER__.context(DEBUGGER__.last_thread).stop_next()
246 @eventExit = true
247 return
248
249 when RequestStepOver
250 DEBUGGER__.context(DEBUGGER__.last_thread).step_over()
251 @eventExit = true
252 return
253
254 when RequestStepOut
255 DEBUGGER__.context(DEBUGGER__.last_thread).step_out()
256 @eventExit = true
257 return
258
259 when RequestStepQuit
260 set_trace_func nil
261 wasDebugging = $debugging
262 $debugging = false
263 @running = nil
264 if @passive
265 progTerminated(42)
266 else
267 DEBUGGER__.context(DEBUGGER__.last_thread).step_quit() if wasDebugging
268 end
269 return
270
271 when RequestContinue
272 special = arg.to_i
273 if special == 0
274 DEBUGGER__.context(DEBUGGER__.last_thread).step_continue()
275 else
276 # special == 1 means a continue while doing a step over
277 # this occurs when an expception is raised doing a step over
278 DEBUGGER__.context(DEBUGGER__.last_thread).step_over()
279 end
280 @eventExit = true
281 return
282
283 when RequestSetFilter
284 scope, filterString = eval("[%s]" % arg)
285 generateFilterObjects(scope.to_i, filterString)
286 return
287
288 when RequestLoad
289 $debugging = true
290 ARGV.clear()
291 wd, fn, args, traceRuby = arg.split("|", -4)
292 @traceRuby = traceRuby.to_i == 1 ? true : false
293 ARGV.concat(eval(args.gsub(/u'/, "'").gsub(/u"/,'"')))
294 $:.insert(0, File.dirname(fn))
295 if wd == ''
296 Dir.chdir($:[0])
297 else
298 Dir.chdir(wd)
299 end
300 @running = fn
301 command = "$0 = '%s'; require '%s'" % [fn, fn]
302 RubyVM::InstructionSequence.compile_option = {
303 trace_instruction: true
304 }
305 set_trace_func proc { |event, file, line, id, binding_, klass, *rest|
306 DEBUGGER__.context.trace_func(event, file, line, id, binding_, klass)
307 }
308 begin
309 eval(command, @debugBinding)
310 rescue DebugQuit
311 # just ignore it
312 rescue Exception => exc
313 unhandled_exception(exc)
314 ensure
315 set_trace_func(nil)
316 @running = nil
317 end
318 return
319
320 when RequestRun
321 $debugging = false
322 ARGV.clear()
323 wd, fn, args = arg.split("|", -3)
324 ARGV.concat(eval(args.gsub(/u'/, "'").gsub(/u"/,'"')))
325 $:.insert(0, File.dirname(fn))
326 if wd == ''
327 Dir.chdir($:[0])
328 else
329 Dir.chdir(wd)
330 end
331 command = "$0 = '%s'; require '%s'" % [fn, fn]
332 @frames = []
333 set_trace_func proc { |event, file, line, id, binding_, klass, *rest|
334 trace_func(event, file, line, id, binding_, klass)
335 }
336 begin
337 eval(command, @debugBinding)
338 rescue SystemExit
339 # ignore it
340 rescue Exception => exc
341 unhandled_exception(exc)
342 ensure
343 set_trace_func(nil)
344 end
345 return
346
347 when RequestShutdown
348 sessionClose()
349 return
350
351 when RequestBreak
352 fn, line, temporary, set, cond = arg.split("@@")
353 line = line.to_i
354 set = set.to_i
355 temporary = temporary.to_i == 1 ? true : false
356
357 if set == 1
358 if cond == 'None'
359 cond = nil
360 end
361 DEBUGGER__.context(DEBUGGER__.last_thread)\
362 .add_break_point(fn, line, temporary, cond)
363 else
364 DEBUGGER__.context(DEBUGGER__.last_thread)\
365 .delete_break_point(fn, line)
366 end
367 return
368
369 when RequestBreakEnable
370 fn, line, enable = arg.split(',')
371 line = line.to_i
372 enable = enable.to_i == 1 ? true : false
373 DEBUGGER__.context(DEBUGGER__.last_thread)\
374 .enable_break_point(fn, line, enable)
375 return
376
377 when RequestBreakIgnore
378 fn, line, count = arg.split(',')
379 line = line.to_i
380 count = count.to_i
381 DEBUGGER__.context(DEBUGGER__.last_thread)\
382 .ignore_break_point(fn, line, count)
383 return
384
385 when RequestWatch
386 cond, temporary, set = arg.split('@@')
387 set = set.to_i
388 temporary = temporary.to_i == 1 ? true : false
389
390 if set == 1
391 DEBUGGER__.context(DEBUGGER__.last_thread)\
392 .add_watch_point(cond, temporary)
393 else
394 DEBUGGER__.context(DEBUGGER__.last_thread).delete_watch_point(cond)
395 end
396 return
397
398 when RequestWatchEnable
399 cond, enable = arg.split(',')
400 enable = enable.to_i == 1 ? true : false
401 DEBUGGER__.context(DEBUGGER__.last_thread)\
402 .enable_watch_point(cond, enable)
403 return
404
405 when RequestWatchIgnore
406 cond, count = arg.split(',')
407 count = count.to_i
408 DEBUGGER__.context(DEBUGGER__.last_thread).ignore_watch_point(cond, count)
409 return
410
411 when RequestEval, RequestExec
412 if not @running
413 binding_ = @shellBinding
414 else
415 binding_ = DEBUGGER__.context(DEBUGGER__.last_thread).current_binding
416 end
417 write("\n")
418 begin
419 value = eval(arg, binding_)
420 rescue Exception => exc
421 list = []
422 list << "%s: %s\n" % \
423 [exc.class, exc.to_s.sub(/stdin:\d+:(in `.*':?)?/, '')]
424 $@.each do |l|
425 break if l =~ /e4dc_ShellBinding/ or l =~ /e4dc_DebugBinding/ or \
426 l =~ /:in `require'$/
427 list << "%s\n" % l.sub(/\(eval\)/, "(e3dc)")
428 end
429 list.each do |entry|
430 write(entry)
431 end
432 else
433 write("=> #{value.inspect}\n")
434 write("#{ResponseOK}\n")
435 end
436 return
437
438 when RequestBanner
439 version = "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
440 write("%s('%s','%s','%s')\n" % \
441 [ResponseBanner, version, Socket.gethostname(), @variant])
442 return
443
444 when RequestCapabilities
445 write("%s%d, 'Ruby'\n" % \
446 [ResponseCapabilities, @@clientCapabilities])
447 return
448
449 when RequestCompletion
450 completionList(arg)
451 return
452
453 else
454 puts "Got unsupported command %s.\n" % cmd
455 return
456 end
457 end
458
459 if @buffer
460 @buffer << "\n" << line
461 @lineno += 1
462 else
463 @buffer = line.dup
464 end
465
466 # check for completeness
467 if not canEval?
468 @pendingResponse = ResponseContinue
469 else
470 command = @buffer.dup
471 @buffer = ""
472 begin
473 res = "=> "
474 if not @running
475 res << eval(command, @shellBinding, "stdin", @lineno).inspect << "\n"
476 else
477 res << eval(command,
478 DEBUGGER__.context(DEBUGGER__.last_thread).get_binding(@framenr),
479 "stdin", @lineno).inspect << "\n"
480 end
481 write(res)
482 rescue SystemExit => exc
483 progTerminated(exc.status)
484 rescue ScriptError, StandardError => exc
485 list = []
486 list << "%s: %s\n" % \
487 [exc.class, exc.to_s.sub(/stdin:\d+:(in `.*':?)?/, '')]
488 $@.each do |l|
489 break if l =~ /e4dc_ShellBinding/ or l =~ /e4dc_DebugBinding/ or \
490 l =~ /:in `require'$/
491 list << "%s\n" % l.sub(/\(eval\)/, "(e3dc)")
492 end
493 list.each do |entry|
494 write(entry)
495 end
496 handleException()
497 end
498 end
499 end
500
501 def canEval?
502 =begin edoc
503 Private method to check if the buffer's contents can be evaluated.
504
505 @return flag indicating if an eval might succeed (boolean)
506 =end
507 indent = 0
508 if @buffer =~ /,\s*$/
509 return false
510 end
511
512 @buffer.split($/).each do |l|
513 if l =~ /^\s*(class|module|def|if|unless|case|while|until|for|begin)\b[^_]/
514 indent += 1
515 end
516 if l =~ /\s*do\s*(\|.*\|)?\s*$/
517 indent += 1
518 end
519 if l =~ /^\s*end\s*$|^\s*end\b[^_]/
520 indent -= 1
521 end
522 if l =~ /\{\s*(\|.*\|)?\s*$/
523 indent += 1
524 end
525 if l =~ /^\s*\}/
526 indent -= 1
527 end
528 end
529
530 if indent > 0
531 return false
532 end
533 return true
534 end
535
536 def trace_func(event, file, line, id, binding_, klass)
537 =begin edoc
538 Method executed by the tracing facility.
539
540 It is used to save the execution context of an exception.
541
542 @param event the tracing event (String)
543 @param file the name of the file being traced (String)
544 @param line the line number being traced (int)
545 @param id object id
546 @param binding_ a binding object
547 @param klass name of a class
548 =end
549 case event
550 when 'line'
551 if @frames[0]
552 @frames[0] = binding_
553 else
554 @frames.unshift binding_
555 end
556
557 when 'call'
558 @frames.unshift binding_
559
560 when 'class'
561 @frames.unshift binding_
562
563 when 'return', 'end'
564 @frames.shift
565
566 when 'end'
567 @frames.shift
568
569 when 'raise'
570 set_trace_func nil
571 end
572 end
573
574 def write(s)
575 =begin edoc
576 Private method to write data to the output stream.
577
578 @param s data to be written (string)
579 =end
580 @writestream.write(s)
581 @writestream.flush()
582 end
583
584 def interact
585 =begin edoc
586 Private method to Interact with the debugger.
587 =end
588 setDescriptors(@readstream, @writestream)
589 $DebugClientInstance = self
590
591 if not @passive
592 # At this point simulate an event loop.
593 eventLoop()
594 end
595 end
596
597 def eventLoop
598 =begin edoc
599 Private method implementing our event loop.
600 =end
601 @eventExit = nil
602
603 while @eventExit == nil
604 wrdy = []
605
606 if AsyncPendingWrite(@writestream) > 0
607 wrdy << @writestream.getSock()
608 end
609
610 if AsyncPendingWrite(@errorstream) > 0
611 wrdy << @errorstream.getSock()
612 end
613
614 rrdy, wrdy, xrdy = select([@readstream.getSock()], wrdy, [])
615
616 if rrdy.include?(@readstream.getSock())
617 readReady(@readstream.fileno())
618 end
619
620 if wrdy.include?(@writestream.getSock())
621 writeReady(@writestream.fileno())
622 end
623
624 if wrdy.include?(@errorstream.getSock())
625 writeReady(@errorstream.fileno())
626 end
627 end
628
629 @eventExit = nil
630 end
631
632 def eventPoll
633 =begin edoc
634 Private method to poll for events like 'set break point'.
635 =end
636
637 # the choice of a ~0.5 second poll interval is arbitrary.
638 lasteventpolltime = @lasteventpolltime ? @lasteventpolltime : Time.now
639 now = Time.now
640 if now - lasteventpolltime < 0.5
641 @lasteventpolltime = lasteventpolltime
642 return
643 else
644 @lasteventpolltime = now
645 end
646
647 wrdy = []
648
649 if AsyncPendingWrite(@writestream) > 0
650 wrdy << @writestream.getSock()
651 end
652
653 if AsyncPendingWrite(@errorstream) > 0
654 wrdy << @errorstream.getSock()
655 end
656
657 rrdy, wrdy, xrdy = select([@readstream.getSock()], wrdy, [], 0)
658
659 if rrdy == nil
660 return
661 end
662
663 if rrdy.include?(@readstream.getSock())
664 readReady(@readstream.fileno())
665 end
666
667 if wrdy.include?(@writestream.getSock())
668 writeReady(@writestream.fileno())
669 end
670
671 if wrdy.include?(@errorstream.getSock())
672 writeReady(@errorstream.fileno())
673 end
674 end
675
676 def connectDebugger(port, remoteAddress=nil, redirect=true)
677 =begin edoc
678 Public method to establish a session with the debugger.
679
680 It opens a network connection to the debugger, connects it to stdin,
681 stdout and stderr and saves these file objects in case the application
682 being debugged redirects them itself.
683
684 @param port the port number to connect to (int)
685 @param remoteAddress the network address of the debug server host (string)
686 @param redirect flag indicating redirection of stdin, stdout and stderr (boolean)
687 =end
688 if remoteAddress == nil
689 sock = TCPSocket.new(DebugAddress, port)
690 else
691 if remoteAddress =~ /@@i/
692 remoteAddress, interface = remoteAddress.split("@@i")
693 else
694 interface = 0
695 end
696 if remoteAddress.downcase =~ /^fe80/
697 remoteAddress = "%s%%%s" % [remoteAddress, interface]
698 end
699 sock = TCPSocket.new(remoteAddress, port)
700 end
701
702 @readstream = AsyncFile.new(sock, "r", "stdin")
703 @writestream = AsyncFile.new(sock, "w", "stdout")
704 @errorstream = AsyncFile.new(sock, "w", "stderr")
705
706 if redirect
707 $stdin = @readstream
708 $stdout = @writestream
709 $stderr = @errorstream
710 end
711 end
712
713 def progTerminated(status)
714 =begin edoc
715 Private method to tell the debugger that the program has terminated.
716
717 @param status the return status
718 =end
719 if status == nil
720 status = 0
721 else
722 begin
723 Integer(status)
724 rescue
725 status = 1
726 end
727 end
728
729 set_trace_func(nil)
730 @running = nil
731 write("%s%d\n" % [ResponseExit, status])
732 end
733
734 def dumpVariables(frmnr, scope, filter)
735 =begin edoc
736 Private method to return the variables of a frame to the debug server.
737
738 @param frmnr distance of frame reported on. 0 is the current frame (int)
739 @param scope 1 to report global variables, 0 for local variables (int)
740 @param filter the indices of variable types to be filtered (list of int)
741 =end
742 if $debugging
743 if scope == 0
744 @framenr = frmnr
745 end
746 binding_, file, line, id = DEBUGGER__.context(DEBUGGER__.last_thread)\
747 .get_frame(frmnr)
748 else
749 binding_ = @frames[frmnr]
750 end
751 varlist = [scope]
752 if scope >= 1
753 # dump global variables
754 vlist = formatVariablesList(global_variables, binding_, scope, filter)
755 elsif scope == 0
756 # dump local variables
757 vlist = formatVariablesList(eval("local_variables", binding_),
758 binding_, scope, filter)
759 end
760 varlist.concat(vlist)
761 write("%s%s\n" % [ResponseVariables, varlist.inspect])
762 end
763
764 def dumpVariable(var, frmnr, scope, filter)
765 =begin edoc
766 Private method to return the variables of a frame to the debug server.
767
768 @param var list encoded name of the requested variable (list of strings)
769 @param frmnr distance of frame reported on. 0 is the current frame (int)
770 @param scope 1 to report global variables, 0 for local variables (int)
771 @param filter the indices of variable types to be filtered (list of int)
772 =end
773 if $debugging
774 binding_, file, line, id = DEBUGGER__.context(DEBUGGER__.last_thread)\
775 .get_frame(frmnr)
776 else
777 binding_ = @frames[frmnr]
778 end
779 varlist = [scope, var]
780 i = 0
781 obj = nil
782 keylist = nil
783 formatSequences = false
784 access = ""
785 isDict = false
786
787 while i < var.length
788 if ["[]", "{}"].include?(var[i][-2..-1])
789 if i+1 == var.length
790 keylist = [var[i][0..-3]]
791 formatSequences = true
792 if access.length == 0
793 access = "#{var[i][0..-3]}"
794 else
795 access << "[#{var[i][0..-3]}]"
796 end
797 if var[i][-2..-1] == "{}"
798 isDict = true
799 end
800 break
801 else
802 if access.length == 0
803 access = "#{var[i][0..-3]}"
804 else
805 access << "[#{var[i][0..-3]}]"
806 end
807 end
808 else
809 if access.length != 0
810 access << "[#{var[i]}]"
811 obj = eval(access, binding_)
812 binding_ = obj.instance_eval{binding()}
813 access = ""
814 else
815 obj = eval(var[i], binding_)
816 binding_ = obj.instance_eval{binding()}
817 end
818 end
819 i += 1
820 end
821 if formatSequences
822 bind = binding_
823 else
824 bind = obj.instance_eval{binding()}
825 end
826 if not keylist
827 keylist = obj.instance_variables
828 access = nil
829 else
830 if access.length != 0
831 obj = eval(access, bind)
832 end
833 if isDict
834 keylist = obj.keys()
835 else
836 keylist = Array.new(obj.length){|i| i}
837 end
838 end
839 vlist = formatVariablesList(keylist, bind, scope, filter,
840 excludeSelf=true, access=access)
841 varlist.concat(vlist)
842 write("%s%s\n" % [ResponseVariable, varlist.inspect])
843 end
844
845 def formatVariablesList(keylist, binding_, scope, filter = [],
846 excludeSelf = false, access = nil)
847 =begin edoc
848 Private method to produce a formated variables list.
849
850 The binding passed in to it is scanned. Variables are
851 only added to the list, if their type is not contained
852 in the filter list and their name doesn't match any of the filter expressions.
853 The formated variables list (a list of lists of 3 values) is returned.
854
855 @param keylist keys of the dictionary
856 @param binding_ the binding to be scanned
857 @param scope 1 to filter using the globals filter, 0 using the locals filter (int).
858 Variables are only added to the list, if their name do not match any of the
859 filter expressions.
860 @param filter the indices of variable types to be filtered. Variables are
861 only added to the list, if their type is not contained in the filter
862 list.
863 @param excludeSelf flag indicating if the self object should be excluded from
864 the listing (boolean)
865 @param access String specifying the access path to (String)
866 @return A list consisting of a list of formatted variables. Each variable
867 entry is a list of three elements, the variable name, its type and
868 value.
869 =end
870 varlist = []
871 if scope >= 1
872 patternFilterObjects = @globalsFilterObjects
873 else
874 patternFilterObjects = @localsFilterObjects
875 begin
876 obj = eval("self", binding_)
877 rescue StandardError, ScriptError
878 obj = nil
879 end
880 if not excludeSelf and obj.class != Object
881 keylist << "self"
882 end
883 end
884
885 keylist.each do |key|
886 # filter based on the filter pattern
887 matched = false
888 patternFilterObjects.each do |pat|
889 if pat.match(key)
890 matched = true
891 break
892 end
893 end
894 next if matched
895
896 if key.to_s == '$KCODE' or key.to_s == '$=' or key.to_s == '$-K'
897 varlist << [key.to_s, "NilClass", "nil"]
898 next
899 end
900
901 begin
902 if access
903 if key.to_s == key
904 key = "'%s'" % key
905 else
906 key = key.to_s
907 end
908 k = "#{access}[%s]" % key
909 obj = eval(k, binding_)
910 else
911 obj = eval(key.to_s, binding_)
912 end
913 rescue NameError
914 next
915 end
916
917 if obj or obj.class == NilClass or obj.class == FalseClass
918 otype = obj.class.to_s
919 if obj.inspect.nil?
920 otype = ""
921 oval = ""
922 else
923 oval = obj.inspect.gsub(/=>/,":")
924 end
925 else
926 otype = ""
927 oval = obj.inspect
928 end
929
930 next if inFilter?(filter, otype, oval)
931
932 if oval.index("#<") == 0
933 addr = extractAddress(oval)
934 oval = "<#{otype} object at #{addr}>"
935 end
936 if obj
937 if obj.class == Array or obj.class == Hash
938 oval = "%d" % obj.length()
939 end
940 end
941 varlist << [key.to_s, otype, oval]
942 end
943 return varlist
944 end
945
946 def extractAddress(var)
947 =begin edoc
948 Private method to extract the address part of an object description.
949
950 @param var object description (String)
951 @return the address contained in the object description (String)
952 =end
953 m = var.match(/^#<.*?:([^:]*?) /)
954 if m
955 return m[1]
956 else
957 return ""
958 end
959 end
960
961 def extractTypeAndAddress(var)
962 =begin edoc
963 Private method to extract the address and type parts of an object description.
964
965 @param var object description (String)
966 @return list containing the type and address contained in the object
967 description (Array of two String)
968 =end
969 m = var.match(/^#<(.*?):(.*?) /)
970 if m
971 return [m[1], m[2]]
972 else
973 return ["", ""]
974 end
975 end
976
977 def inFilter?(filter, otype, oval)
978 =begin edoc
979 Private method to check, if a variable is to be filtered based on its type.
980
981 @param filter the indices of variable types to be filtered (Array of int.
982 @param otype type of the variable to be checked (String)
983 @param oval variable value to be checked (String)
984 @return flag indicating, whether the variable should be filtered (boolean)
985 =end
986 cindex = ConfigVarTypeStrings.index(otype)
987 if cindex == nil
988 if oval.index("#<") == 0
989 if filter.include?(ConfigVarTypeStrings.index("instance"))
990 return true
991 else
992 return false
993 end
994 elsif ['FalseClass', 'TrueClass'].include?(otype)
995 if filter.include?(ConfigVarTypeStrings.index("bool"))
996 return true
997 else
998 return false
999 end
1000 else
1001 if filter.include?(ConfigVarTypeStrings.index("other"))
1002 return true
1003 else
1004 return false
1005 end
1006 end
1007 end
1008 if filter.include?(cindex)
1009 return true
1010 end
1011 return false
1012 end
1013
1014 def generateFilterObjects(scope, filterString)
1015 =begin edoc
1016 Private method to convert a filter string to a list of filter objects.
1017
1018 @param scope 1 to generate filter for global variables, 0 for local variables (int)
1019 @param filterString string of filter patterns separated by ';'
1020 =end
1021 patternFilterObjects = []
1022 for pattern in filterString.split(';')
1023 patternFilterObjects << Regexp.compile('^%s$' % pattern)
1024 end
1025 if scope == 1
1026 @globalsFilterObjects = patternFilterObjects[0..-1]
1027 else
1028 @localsFilterObjects = patternFilterObjects[0..-1]
1029 end
1030 end
1031
1032 def completionList(text)
1033 =begin edoc
1034 Method used to handle the command completion request
1035
1036 @param text the text to be completed (string)
1037 =end
1038 completions = @completer.complete(text).compact.sort.uniq
1039 write("%s%s||%s\n" % [ResponseCompletion, completions.inspect, text])
1040 end
1041
1042 def startProgInDebugger(progargs, wd = '', host = nil,
1043 port = nil, exceptions = true, traceRuby = false, redirect=true)
1044 =begin edoc
1045 Method used to start the remote debugger.
1046
1047 @param progargs commandline for the program to be debugged
1048 (list of strings)
1049 @param wd working directory for the program execution (string)
1050 @param host hostname of the debug server (string)
1051 @param port portnumber of the debug server (int)
1052 @param exceptions flag to enable exception reporting of the IDE (boolean)
1053 @param traceRuby flag to enable tracing into the Ruby library
1054 @param redirect flag indicating redirection of stdin, stdout and stderr (boolean)
1055 =end
1056 $debugging = true
1057
1058 if host == nil
1059 host = ENV.fetch('ERICHOST', 'localhost')
1060 end
1061 if port == nil
1062 port = ENV.fetch('ERICPORT', 42424)
1063 end
1064
1065 connectDebugger(port, host, redirect) #TCPSocket.gethostbyname(host)[3])
1066 DEBUGGER__.attach(self)
1067
1068 @traceRuby = traceRuby
1069
1070 fn = progargs.shift
1071 fn = File.expand_path(fn)
1072
1073 ARGV.clear()
1074 ARGV.concat(progargs)
1075 $:.insert(0, File.dirname(fn))
1076 if wd == ''
1077 Dir.chdir($:[0])
1078 else
1079 Dir.chdir(wd)
1080 end
1081 @running = fn
1082
1083 @passive = true
1084 write("%s%s|%d\n" % [PassiveStartup, @running, exceptions ? 1 : 0])
1085 interact()
1086
1087 command = "$0 = '%s'; require '%s'" % [fn, fn]
1088 set_trace_func proc { |event, file, line, id, binding_, klass, *rest|
1089 DEBUGGER__.context.trace_func(event, file, line, id, binding_, klass)
1090 }
1091 begin
1092 eval(command, @debugBinding)
1093 rescue DebugQuit
1094 # just ignore it
1095 rescue Exception => exc
1096 unhandled_exception(exc)
1097 ensure
1098 set_trace_func(nil)
1099 @running = nil
1100 end
1101 # just a short delay because client might shut down too quickly otherwise
1102 sleep(1)
1103 end
1104
1105 def main
1106 =begin edoc
1107 Public method implementing the main method.
1108 =end
1109 if ARGV.include?('--')
1110 args = ARGV[0..-1]
1111 host = nil
1112 port = nil
1113 wd = ''
1114 traceRuby = false
1115 exceptions = true
1116 redirect = true
1117 while args[0]
1118 if args[0] == '-h'
1119 host = args[1]
1120 args.shift
1121 args.shift
1122 elsif args[0] == '-p'
1123 port = args[1]
1124 args.shift
1125 args.shift
1126 elsif args[0] == '-w'
1127 wd = args[1]
1128 args.shift
1129 args.shift
1130 elsif args[0] == '-t'
1131 traceRuby = true
1132 args.shift
1133 elsif args[0] == '-e'
1134 exceptions = false
1135 args.shift
1136 elsif args[0] == '-n'
1137 redirect = false
1138 args.shift
1139 elsif args[0] == '--'
1140 args.shift
1141 break
1142 else # unknown option
1143 args.shift
1144 end
1145 end
1146 if args.length == 0
1147 STDOUT << "No program given. Aborting!"
1148 else
1149 startProgInDebugger(args, wd, host, port,
1150 exceptions = exceptions, traceRuby = traceRuby,
1151 redirect = redirect)
1152 end
1153 else
1154 if ARGV[0] == '--no-encoding'
1155 # just ignore it, it's here to be compatible with python debugger
1156 ARGV.shift
1157 end
1158
1159 begin
1160 port = ARGV[0].to_i
1161 rescue
1162 port = -1
1163 end
1164
1165 begin
1166 redirect = ARGV[1].to_i
1167 redirect = redirect == 1 ? true : false
1168 rescue
1169 redirect = true
1170 end
1171
1172 begin
1173 remoteAddress = ARGV[2]
1174 rescue
1175 remoteAddress = nil
1176 end
1177
1178 ARGV.clear()
1179 $:.insert(0,"")
1180 if port >= 0
1181 connectDebugger(port, remoteAddress, redirect)
1182 DEBUGGER__.attach(self)
1183 interact()
1184 end
1185 end
1186 end
1187 end

eric ide

mercurial