debug 1.6.3 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/debug/prelude.rb CHANGED
File without changes
data/lib/debug/server.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'socket'
4
- require 'etc'
5
4
  require_relative 'config'
6
5
  require_relative 'version'
7
6
 
@@ -22,6 +21,7 @@ module DEBUGGER__
22
21
 
23
22
  class Terminate < StandardError; end
24
23
  class GreetingError < StandardError; end
24
+ class RetryConnection < StandardError; end
25
25
 
26
26
  def deactivate
27
27
  @reader_thread.raise Terminate
@@ -78,6 +78,8 @@ module DEBUGGER__
78
78
  next
79
79
  rescue Terminate
80
80
  raise # should catch at outer scope
81
+ rescue RetryConnection
82
+ next
81
83
  rescue => e
82
84
  DEBUGGER__.warn "ReaderThreadError: #{e}"
83
85
  pp e.backtrace
@@ -128,6 +130,8 @@ module DEBUGGER__
128
130
  def greeting
129
131
  case g = @sock.gets
130
132
  when /^info cookie:\s+(.*)$/
133
+ require 'etc'
134
+
131
135
  check_cookie $1
132
136
  @sock.puts "PID: #{Process.pid}, $0: #{$0}"
133
137
  @sock.puts "debug #{VERSION} on #{RUBY_DESCRIPTION}"
@@ -140,7 +144,8 @@ module DEBUGGER__
140
144
 
141
145
  # TODO: protocol version
142
146
  if v != VERSION
143
- raise GreetingError, "Incompatible version (server:#{VERSION} and client:#{$1})"
147
+ @sock.puts msg = "out DEBUGGER: Incompatible version (server:#{VERSION} and client:#{$1})"
148
+ raise GreetingError, msg
144
149
  end
145
150
  parse_option(params)
146
151
 
@@ -157,16 +162,13 @@ module DEBUGGER__
157
162
  @need_pause_at_first = false
158
163
  dap_setup @sock.read($1.to_i)
159
164
 
160
- when /^GET \/.* HTTP\/1.1/
165
+ when /^GET\s\/json\sHTTP\/1.1/, /^GET\s\/json\/version\sHTTP\/1.1/, /^GET\s\/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}\sHTTP\/1.1/
166
+ # The reason for not using @uuid here is @uuid is nil if users run debugger without `--open=chrome`.
167
+
161
168
  require_relative 'server_cdp'
162
169
 
163
170
  self.extend(UI_CDP)
164
- @repl = false
165
- @need_pause_at_first = false
166
- CONFIG.set_config no_color: true
167
-
168
- @ws_server = UI_CDP::WebSocketServer.new(@sock)
169
- @ws_server.handshake
171
+ send_chrome_response g
170
172
  else
171
173
  raise GreetingError, "Unknown greeting message: #{g}"
172
174
  end
@@ -174,17 +176,17 @@ module DEBUGGER__
174
176
 
175
177
  def process
176
178
  while true
177
- DEBUGGER__.info "sleep IO.select"
178
- r = IO.select([@sock])
179
- DEBUGGER__.info "wakeup IO.select"
179
+ DEBUGGER__.debug{ "sleep IO.select" }
180
+ _r = IO.select([@sock])
181
+ DEBUGGER__.debug{ "wakeup IO.select" }
180
182
 
181
183
  line = @session.process_group.sync do
182
184
  unless IO.select([@sock], nil, nil, 0)
183
- DEBUGGER__.info "UI_Server can not read"
185
+ DEBUGGER__.debug{ "UI_Server can not read" }
184
186
  break :can_not_read
185
187
  end
186
188
  @sock.gets&.chomp.tap{|line|
187
- DEBUGGER__.info "UI_Server received: #{line}"
189
+ DEBUGGER__.debug{ "UI_Server received: #{line}" }
188
190
  }
189
191
  end
190
192
 
@@ -340,12 +342,12 @@ module DEBUGGER__
340
342
  if @repl
341
343
  raise "not in subsession, but received: #{line.inspect}" unless @session.in_subsession?
342
344
  line = "input #{Process.pid}"
343
- DEBUGGER__.info "send: #{line}"
345
+ DEBUGGER__.debug{ "send: #{line}" }
344
346
  s.puts line
345
347
  end
346
348
  sleep 0.01 until @q_msg
347
349
  @q_msg.pop.tap{|msg|
348
- DEBUGGER__.info "readline: #{msg.inspect}"
350
+ DEBUGGER__.debug{ "readline: #{msg.inspect}" }
349
351
  }
350
352
  end || 'continue')
351
353
 
@@ -361,7 +363,7 @@ module DEBUGGER__
361
363
  Process.kill(TRAP_SIGNAL, Process.pid)
362
364
  end
363
365
 
364
- def quit n
366
+ def quit n, &_b
365
367
  # ignore n
366
368
  sock do |s|
367
369
  s.puts "quit"
@@ -395,6 +397,7 @@ module DEBUGGER__
395
397
  raise "Specify digits for port number"
396
398
  end
397
399
  end
400
+ @uuid = nil # for CDP
398
401
 
399
402
  super()
400
403
  end
@@ -402,11 +405,12 @@ module DEBUGGER__
402
405
  def chrome_setup
403
406
  require_relative 'server_cdp'
404
407
 
405
- unless @chrome_pid = UI_CDP.setup_chrome(@local_addr.inspect_sockaddr)
408
+ @uuid = SecureRandom.uuid
409
+ unless @chrome_pid = UI_CDP.setup_chrome(@local_addr.inspect_sockaddr, @uuid)
406
410
  DEBUGGER__.warn <<~EOS
407
411
  With Chrome browser, type the following URL in the address-bar:
408
412
 
409
- devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{@local_addr.inspect_sockaddr}/#{SecureRandom.uuid}
413
+ devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{@local_addr.inspect_sockaddr}/#{@uuid}
410
414
 
411
415
  EOS
412
416
  end
@@ -434,7 +438,7 @@ module DEBUGGER__
434
438
  #
435
439
  EOS
436
440
 
437
- case CONFIG[:open_frontend]
441
+ case CONFIG[:open]
438
442
  when 'chrome'
439
443
  chrome_setup
440
444
  when 'vscode'
@@ -491,7 +495,7 @@ module DEBUGGER__
491
495
  end
492
496
 
493
497
  ::DEBUGGER__.warn "Debugger can attach via UNIX domain socket (#{@sock_path})"
494
- vscode_setup @sock_path if CONFIG[:open_frontend] == 'vscode'
498
+ vscode_setup @sock_path if CONFIG[:open] == 'vscode'
495
499
 
496
500
  begin
497
501
  Socket.unix_server_loop @sock_path do |sock, client|
@@ -7,13 +7,18 @@ require 'securerandom'
7
7
  require 'stringio'
8
8
  require 'open3'
9
9
  require 'tmpdir'
10
+ require 'tempfile'
11
+ require 'timeout'
10
12
 
11
13
  module DEBUGGER__
12
14
  module UI_CDP
13
15
  SHOW_PROTOCOL = ENV['RUBY_DEBUG_CDP_SHOW_PROTOCOL'] == '1'
14
16
 
17
+ class UnsupportedError < StandardError; end
18
+ class NotFoundChromeEndpointError < StandardError; end
19
+
15
20
  class << self
16
- def setup_chrome addr
21
+ def setup_chrome addr, uuid
17
22
  return if CONFIG[:chrome_path] == ''
18
23
 
19
24
  port, path, pid = run_new_chrome
@@ -39,6 +44,8 @@ module DEBUGGER__
39
44
  }
40
45
  when res['id'] == 2
41
46
  s_id = res.dig('result', 'sessionId')
47
+ # TODO: change id
48
+ ws_client.send sessionId: s_id, id: 100, method: 'Network.enable'
42
49
  ws_client.send sessionId: s_id, id: 3,
43
50
  method: 'Page.enable'
44
51
  when res['id'] == 3
@@ -51,57 +58,191 @@ module DEBUGGER__
51
58
  ws_client.send sessionId: s_id, id: 5,
52
59
  method: 'Page.navigate',
53
60
  params: {
54
- url: "devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{addr}/#{SecureRandom.uuid}",
61
+ url: "devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{addr}/#{uuid}",
55
62
  frameId: f_id
56
63
  }
57
- when res['method'] == 'Page.loadEventFired'
64
+ when res['method'] == 'Network.webSocketWillSendHandshakeRequest'
65
+ s_id = res['sessionId']
66
+ # Display the console by entering ESC key
67
+ ws_client.send sessionId: s_id, id: 101, # TODO: change id
68
+ method:"Input.dispatchKeyEvent",
69
+ params: {
70
+ type:"keyDown",
71
+ windowsVirtualKeyCode:27 # ESC key
72
+ }
73
+ when res['id'] == 101
58
74
  break
59
75
  end
60
76
  end
61
77
  pid
62
- rescue Errno::ENOENT
78
+ rescue Errno::ENOENT, UnsupportedError, NotFoundChromeEndpointError
63
79
  nil
64
80
  end
65
81
 
66
- def get_chrome_path
67
- return CONFIG[:chrome_path] if CONFIG[:chrome_path]
82
+ TIMEOUT_SEC = 5
83
+
84
+ def run_new_chrome
85
+ path = CONFIG[:chrome_path]
86
+
87
+ data = nil
88
+ port = nil
89
+ wait_thr = nil
68
90
 
69
91
  # The process to check OS is based on `selenium` project.
70
92
  case RbConfig::CONFIG['host_os']
71
93
  when /mswin|msys|mingw|cygwin|emc/
72
- 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
94
+ if path.nil?
95
+ candidates = ['C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe']
96
+ path = get_chrome_path candidates
97
+ end
98
+ uuid = SecureRandom.uuid
99
+ # The path is based on https://github.com/sindresorhus/open/blob/v8.4.0/index.js#L128.
100
+ stdin, stdout, stderr, wait_thr = *Open3.popen3("#{ENV['SystemRoot']}\\System32\\WindowsPowerShell\\v1.0\\powershell")
101
+ tf = Tempfile.create(['debug-', '.txt'])
102
+
103
+ stdin.puts("Start-process '#{path}' -Argumentlist '--remote-debugging-port=0', '--no-first-run', '--no-default-browser-check', '--user-data-dir=C:\\temp' -Wait -RedirectStandardError #{tf.path}")
104
+ stdin.close
105
+ stdout.close
106
+ stderr.close
107
+ port, path = get_devtools_endpoint(tf.path)
108
+
109
+ at_exit{
110
+ DEBUGGER__.skip_all
111
+
112
+ stdin, stdout, stderr, wait_thr = *Open3.popen3("#{ENV['SystemRoot']}\\System32\\WindowsPowerShell\\v1.0\\powershell")
113
+ stdin.puts("Stop-process -Name chrome")
114
+ stdin.close
115
+ stdout.close
116
+ stderr.close
117
+ tf.close
118
+ begin
119
+ File.unlink(tf)
120
+ rescue Errno::EACCES
121
+ end
122
+ }
73
123
  when /darwin|mac os/
74
- '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome'
124
+ path = path || '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome'
125
+ dir = Dir.mktmpdir
126
+ # The command line flags are based on: https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging/Chrome_Desktop#connecting.
127
+ stdin, stdout, stderr, wait_thr = *Open3.popen3("#{path} --remote-debugging-port=0 --no-first-run --no-default-browser-check --user-data-dir=#{dir}")
128
+ stdin.close
129
+ stdout.close
130
+ data = stderr.readpartial 4096
131
+ stderr.close
132
+ if data.match /DevTools listening on ws:\/\/127.0.0.1:(\d+)(.*)/
133
+ port = $1
134
+ path = $2
135
+ end
136
+
137
+ at_exit{
138
+ DEBUGGER__.skip_all
139
+ FileUtils.rm_rf dir
140
+ }
75
141
  when /linux/
76
- 'google-chrome'
142
+ path = path || 'google-chrome'
143
+ dir = Dir.mktmpdir
144
+ # The command line flags are based on: https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging/Chrome_Desktop#connecting.
145
+ stdin, stdout, stderr, wait_thr = *Open3.popen3("#{path} --remote-debugging-port=0 --no-first-run --no-default-browser-check --user-data-dir=#{dir}")
146
+ stdin.close
147
+ stdout.close
148
+ data = ''
149
+ begin
150
+ Timeout.timeout(TIMEOUT_SEC) do
151
+ until data.match?(/DevTools listening on ws:\/\/127.0.0.1:\d+.*/)
152
+ data = stderr.readpartial 4096
153
+ end
154
+ end
155
+ rescue Exception
156
+ raise NotFoundChromeEndpointError
157
+ end
158
+ stderr.close
159
+ if data.match /DevTools listening on ws:\/\/127.0.0.1:(\d+)(.*)/
160
+ port = $1
161
+ path = $2
162
+ end
163
+
164
+ at_exit{
165
+ DEBUGGER__.skip_all
166
+ FileUtils.rm_rf dir
167
+ }
77
168
  else
78
- raise "Unsupported OS"
169
+ raise UnsupportedError
79
170
  end
171
+
172
+ [port, path, wait_thr.pid]
80
173
  end
81
174
 
82
- def run_new_chrome
83
- dir = Dir.mktmpdir
84
- # The command line flags are based on: https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging/Chrome_Desktop#connecting
85
- stdin, stdout, stderr, wait_thr = *Open3.popen3("#{get_chrome_path} --remote-debugging-port=0 --no-first-run --no-default-browser-check --user-data-dir=#{dir}")
86
- stdin.close
87
- stdout.close
88
-
89
- data = stderr.readpartial 4096
90
- if data.match /DevTools listening on ws:\/\/127.0.0.1:(\d+)(.*)/
91
- port = $1
92
- path = $2
175
+ def get_chrome_path candidates
176
+ candidates.each{|c|
177
+ if File.exist? c
178
+ return c
179
+ end
180
+ }
181
+ raise UnsupportedError
182
+ end
183
+
184
+ ITERATIONS = 50
185
+
186
+ def get_devtools_endpoint tf
187
+ i = 1
188
+ while i < ITERATIONS
189
+ i += 1
190
+ if File.exist?(tf) && data = File.read(tf)
191
+ if data.match /DevTools listening on ws:\/\/127.0.0.1:(\d+)(.*)/
192
+ port = $1
193
+ path = $2
194
+ return [port, path]
195
+ end
196
+ end
197
+ sleep 0.1
93
198
  end
94
- stderr.close
199
+ raise NotFoundChromeEndpointError
200
+ end
201
+ end
95
202
 
96
- at_exit{
97
- CONFIG[:skip_path] = [//] # skip all
98
- FileUtils.rm_rf dir
203
+ def send_chrome_response req
204
+ @repl = false
205
+ case req
206
+ when /^GET\s\/json\/version\sHTTP\/1.1/
207
+ body = {
208
+ Browser: "ruby/v#{RUBY_VERSION}",
209
+ 'Protocol-Version': "1.1"
99
210
  }
100
-
101
- [port, path, wait_thr.pid]
211
+ send_http_res body
212
+ raise UI_ServerBase::RetryConnection
213
+
214
+ when /^GET\s\/json\sHTTP\/1.1/
215
+ @uuid = @uuid || SecureRandom.uuid
216
+ addr = @local_addr.inspect_sockaddr
217
+ body = [{
218
+ description: "ruby instance",
219
+ devtoolsFrontendUrl: "devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=#{addr}/#{@uuid}",
220
+ id: @uuid,
221
+ title: $0,
222
+ type: "node",
223
+ url: "file://#{File.absolute_path($0)}",
224
+ webSocketDebuggerUrl: "ws://#{addr}/#{@uuid}"
225
+ }]
226
+ send_http_res body
227
+ raise UI_ServerBase::RetryConnection
228
+
229
+ when /^GET\s\/(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})\sHTTP\/1.1/
230
+ raise 'Incorrect uuid' unless $1 == @uuid
231
+
232
+ @need_pause_at_first = false
233
+ CONFIG.set_config no_color: true
234
+
235
+ @ws_server = WebSocketServer.new(@sock)
236
+ @ws_server.handshake
102
237
  end
103
238
  end
104
239
 
240
+ def send_http_res body
241
+ json = JSON.generate body
242
+ header = "HTTP/1.0 200 OK\r\nContent-Type: application/json; charset=UTF-8\r\nCache-Control: no-cache\r\nContent-Length: #{json.bytesize}\r\n\r\n"
243
+ @sock.puts "#{header}#{json}"
244
+ end
245
+
105
246
  module WebSocketUtils
106
247
  class Frame
107
248
  attr_reader :b
@@ -528,12 +669,6 @@ module DEBUGGER__
528
669
 
529
670
  ## Called by the SESSION thread
530
671
 
531
- def readline prompt
532
- return 'c' unless @q_msg
533
-
534
- @q_msg.pop || 'kill!'
535
- end
536
-
537
672
  def respond req, **result
538
673
  send_response req, **result
539
674
  end
@@ -561,6 +696,28 @@ module DEBUGGER__
561
696
  end
562
697
 
563
698
  class Session
699
+ # FIXME: unify this method with ThreadClient#propertyDescriptor.
700
+ def get_type obj
701
+ case obj
702
+ when Array
703
+ ['object', 'array']
704
+ when Hash
705
+ ['object', 'map']
706
+ when String
707
+ ['string']
708
+ when TrueClass, FalseClass
709
+ ['boolean']
710
+ when Symbol
711
+ ['symbol']
712
+ when Integer, Float
713
+ ['number']
714
+ when Exception
715
+ ['object', 'error']
716
+ else
717
+ ['object']
718
+ end
719
+ end
720
+
564
721
  def fail_response req, **result
565
722
  @ui.respond_fail req, **result
566
723
  return :retry
@@ -584,17 +741,38 @@ module DEBUGGER__
584
741
  code: INVALID_PARAMS,
585
742
  message: "'callFrameId' is an invalid"
586
743
  end
587
- when 'Runtime.getProperties'
588
- oid = req.dig('params', 'objectId')
744
+ when 'Runtime.getProperties', 'Runtime.getExceptionDetails'
745
+ oid = req.dig('params', 'objectId') || req.dig('params', 'errorObjectId')
589
746
  if ref = @obj_map[oid]
590
747
  case ref[0]
591
748
  when 'local'
592
749
  frame_id = ref[1]
593
750
  fid = @frame_map[frame_id]
594
751
  request_tc [:cdp, :scope, req, fid]
752
+ when 'global'
753
+ vars = global_variables.sort.map do |name|
754
+ gv = eval(name.to_s)
755
+ prop = {
756
+ name: name,
757
+ value: {
758
+ description: gv.inspect
759
+ },
760
+ configurable: true,
761
+ enumerable: true
762
+ }
763
+ type, subtype = get_type(gv)
764
+ prop[:value][:type] = type
765
+ prop[:value][:subtype] = subtype if subtype
766
+ prop
767
+ end
768
+
769
+ @ui.respond req, result: vars
770
+ return :retry
595
771
  when 'properties'
596
772
  request_tc [:cdp, :properties, req, oid]
597
- when 'script', 'global'
773
+ when 'exception'
774
+ request_tc [:cdp, :exception, req, oid]
775
+ when 'script'
598
776
  # TODO: Support script and global types
599
777
  @ui.respond req, result: []
600
778
  return :retry
@@ -730,6 +908,9 @@ module DEBUGGER__
730
908
  frame[:scriptId] = s_id
731
909
  end
732
910
  }
911
+ if oid = exc[:exception][:objectId]
912
+ @obj_map[oid] = ['exception']
913
+ end
733
914
  end
734
915
  rs = result.dig(:response, :result)
735
916
  [rs].each{|obj|
@@ -767,6 +948,8 @@ module DEBUGGER__
767
948
  }
768
949
  }
769
950
  @ui.respond req, **result
951
+ when :exception
952
+ @ui.respond req, **result
770
953
  end
771
954
  end
772
955
  end
@@ -872,8 +1055,8 @@ module DEBUGGER__
872
1055
  result = b.local_variable_get(expr)
873
1056
  rescue NameError
874
1057
  # try to check method
875
- if b.receiver.respond_to? expr, include_all: true
876
- result = b.receiver.method(expr)
1058
+ if M_RESPOND_TO_P.bind_call(b.receiver, expr, include_all: true)
1059
+ result = M_METHOD.bind_call(b.receiver, expr)
877
1060
  else
878
1061
  message = "Error: Can not evaluate: #{expr.inspect}"
879
1062
  end
@@ -886,35 +1069,7 @@ module DEBUGGER__
886
1069
  result = current_frame.binding.eval(expr.to_s, '(DEBUG CONSOLE)')
887
1070
  rescue Exception => e
888
1071
  result = e
889
- b = result.backtrace.map{|e| " #{e}\n"}
890
- frames = [
891
- {
892
- columnNumber: 0,
893
- functionName: 'eval',
894
- lineNumber: 0,
895
- url: ''
896
- }
897
- ]
898
- e.backtrace_locations&.each do |loc|
899
- break if loc.path == __FILE__
900
- path = loc.absolute_path || loc.path
901
- frames << {
902
- columnNumber: 0,
903
- functionName: loc.base_label,
904
- lineNumber: loc.lineno - 1,
905
- url: path
906
- }
907
- end
908
- res[:exceptionDetails] = {
909
- exceptionId: 1,
910
- text: 'Uncaught',
911
- lineNumber: 0,
912
- columnNumber: 0,
913
- exception: evaluate_result(result),
914
- stackTrace: {
915
- callFrames: frames
916
- }
917
- }
1072
+ res[:exceptionDetails] = exceptionDetails(e, 'Uncaught')
918
1073
  ensure
919
1074
  output = $stdout.string
920
1075
  $stdout = orig_stdout
@@ -987,13 +1142,51 @@ module DEBUGGER__
987
1142
  ]
988
1143
  end
989
1144
 
990
- result += obj.instance_variables.map{|iv|
991
- variable(iv, obj.instance_variable_get(iv))
1145
+ result += M_INSTANCE_VARIABLES.bind_call(obj).map{|iv|
1146
+ variable(iv, M_INSTANCE_VARIABLE_GET.bind_call(obj, iv))
992
1147
  }
993
- prop += [internalProperty('#class', obj.class)]
1148
+ prop += [internalProperty('#class', M_CLASS.bind_call(obj))]
994
1149
  end
995
1150
  event! :cdp_result, :properties, req, result: result, internalProperties: prop
1151
+ when :exception
1152
+ oid = args.shift
1153
+ exc = nil
1154
+ if obj = @obj_map[oid]
1155
+ exc = exceptionDetails obj, obj.to_s
1156
+ end
1157
+ event! :cdp_result, :exception, req, exceptionDetails: exc
1158
+ end
1159
+ end
1160
+
1161
+ def exceptionDetails exc, text
1162
+ frames = [
1163
+ {
1164
+ columnNumber: 0,
1165
+ functionName: 'eval',
1166
+ lineNumber: 0,
1167
+ url: ''
1168
+ }
1169
+ ]
1170
+ exc.backtrace_locations&.each do |loc|
1171
+ break if loc.path == __FILE__
1172
+ path = loc.absolute_path || loc.path
1173
+ frames << {
1174
+ columnNumber: 0,
1175
+ functionName: loc.base_label,
1176
+ lineNumber: loc.lineno - 1,
1177
+ url: path
1178
+ }
996
1179
  end
1180
+ {
1181
+ exceptionId: 1,
1182
+ text: text,
1183
+ lineNumber: 0,
1184
+ columnNumber: 0,
1185
+ exception: evaluate_result(exc),
1186
+ stackTrace: {
1187
+ callFrames: frames
1188
+ }
1189
+ }
997
1190
  end
998
1191
 
999
1192
  def search_const b, expr
@@ -1001,7 +1194,11 @@ module DEBUGGER__
1001
1194
  [Object, *b.eval('::Module.nesting')].reverse_each{|mod|
1002
1195
  if cs.all?{|c|
1003
1196
  if mod.const_defined?(c)
1004
- mod = mod.const_get(c)
1197
+ begin
1198
+ mod = mod.const_get(c)
1199
+ rescue Exception
1200
+ false
1201
+ end
1005
1202
  else
1006
1203
  false
1007
1204
  end
@@ -1045,25 +1242,29 @@ module DEBUGGER__
1045
1242
  v = prop[:value]
1046
1243
  v.delete :value
1047
1244
  v[:subtype] = subtype if subtype
1048
- v[:className] = obj.class
1245
+ v[:className] = (klass = M_CLASS.bind_call(obj)).name || klass.to_s
1049
1246
  end
1050
1247
  prop
1051
1248
  end
1052
1249
 
1053
1250
  def preview_ value, hash, overflow
1251
+ # The reason for not using "map" method is to prevent the object overriding it from causing bugs.
1252
+ # https://github.com/ruby/debug/issues/781
1253
+ props = []
1254
+ hash.each{|k, v|
1255
+ pd = propertyDescriptor k, v
1256
+ props << {
1257
+ name: pd[:name],
1258
+ type: pd[:value][:type],
1259
+ value: pd[:value][:description]
1260
+ }
1261
+ }
1054
1262
  {
1055
1263
  type: value[:type],
1056
1264
  subtype: value[:subtype],
1057
1265
  description: value[:description],
1058
1266
  overflow: overflow,
1059
- properties: hash.map{|k, v|
1060
- pd = propertyDescriptor k, v
1061
- {
1062
- name: pd[:name],
1063
- type: pd[:value][:type],
1064
- value: pd[:value][:description]
1065
- }
1066
- }
1267
+ properties: props
1067
1268
  }
1068
1269
  end
1069
1270