Skip to content

Commit 3d0eebb

Browse files
committed
Fix incorrect bind_call backport and extract Reflection methods
1 parent 5cc4655 commit 3d0eebb

File tree

8 files changed

+214
-44
lines changed

8 files changed

+214
-44
lines changed

lib/debug/mirrors/variable_inspector.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ def named_members_of obj
4646
end
4747

4848
unless defined?(DEBUGGER__::NaiveString) && DEBUGGER__::NaiveString === obj
49-
members += M_INSTANCE_VARIABLES.bind_call(obj).sort.map{|iv|
50-
Member.new(name: iv, value: M_INSTANCE_VARIABLE_GET.bind_call(obj, iv))
49+
members += Reflection.instance_variables_of(obj).sort.map{|iv|
50+
Member.new(name: iv, value: Reflection.instance_variable_get_from(obj, iv))
5151
}
52-
members.unshift Member.internal(name: '#class', value: M_CLASS.bind_call(obj))
52+
members.unshift Member.internal(name: '#class', value: Reflection.class_of(obj))
5353
end
5454

5555
members

lib/debug/reflection.rb

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# frozen_string_literal: true
2+
3+
module DEBUGGER__
4+
module Reflection
5+
module_function def instance_variables_of(o)
6+
M_INSTANCE_VARIABLES.bind_call(o)
7+
end
8+
9+
module_function def instance_variable_get_from(o, name)
10+
M_INSTANCE_VARIABLE_GET.bind_call(o, name)
11+
end
12+
13+
module_function def class_of(o)
14+
M_CLASS.bind_call(o)
15+
end
16+
17+
module_function def singleton_class_of(o)
18+
M_SINGLETON_CLASS.bind_call(o)
19+
end
20+
21+
module_function def is_kind_of?(object, type)
22+
M_KIND_OF_P.bind_call(object, type)
23+
end
24+
25+
module_function def responds_to?(object, message, include_all: false)
26+
M_RESPOND_TO_P.bind_call(object, message, include_all)
27+
end
28+
29+
module_function def method_of(type, method_name)
30+
M_METHOD.bind_call(type, method_name)
31+
end
32+
33+
module_function def object_id_of(o)
34+
M_OBJECT_ID.bind_call(o)
35+
end
36+
37+
module_function def name_of(type)
38+
M_NAME.bind_call(type)
39+
end
40+
41+
M_INSTANCE_VARIABLES = Kernel.instance_method(:instance_variables)
42+
M_INSTANCE_VARIABLE_GET = Kernel.instance_method(:instance_variable_get)
43+
M_CLASS = Kernel.instance_method(:class)
44+
M_SINGLETON_CLASS = Kernel.instance_method(:singleton_class)
45+
M_KIND_OF_P = Kernel.instance_method(:kind_of?)
46+
M_RESPOND_TO_P = Kernel.instance_method(:respond_to?)
47+
M_METHOD = Kernel.instance_method(:method)
48+
M_OBJECT_ID = Kernel.instance_method(:object_id)
49+
M_NAME = Module.instance_method(:name)
50+
51+
private_constant(
52+
:M_INSTANCE_VARIABLES,
53+
:M_INSTANCE_VARIABLE_GET,
54+
:M_CLASS,
55+
:M_SINGLETON_CLASS,
56+
:M_KIND_OF_P,
57+
:M_RESPOND_TO_P,
58+
:M_METHOD,
59+
:M_OBJECT_ID,
60+
:M_NAME,
61+
)
62+
end
63+
end
64+
65+
# for Ruby 2.6 compatibility
66+
unless UnboundMethod.method_defined?(:bind_call)
67+
class UnboundMethod
68+
def bind_call(receiver, *args, &block)
69+
bind(receiver).call(*args, &block)
70+
end
71+
end
72+
end

lib/debug/server_cdp.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,8 +1060,8 @@ def process_cdp args
10601060
result = b.local_variable_get(expr)
10611061
rescue NameError
10621062
# try to check method
1063-
if M_RESPOND_TO_P.bind_call(b.receiver, expr, include_all: true)
1064-
result = M_METHOD.bind_call(b.receiver, expr)
1063+
if Reflection.responds_to?(b.receiver, expr, include_all: true)
1064+
result = Reflection.method_of(b.receiver, expr)
10651065
else
10661066
message = "Error: Can not evaluate: #{expr.inspect}"
10671067
end
@@ -1226,7 +1226,7 @@ def propertyDescriptor_ name, obj, type, description: nil, subtype: nil
12261226
v = prop[:value]
12271227
v.delete :value
12281228
v[:subtype] = subtype if subtype
1229-
v[:className] = (klass = M_CLASS.bind_call(obj)).name || klass.to_s
1229+
v[:className] = (klass = Reflection.class_of(obj)).name || klass.to_s
12301230
end
12311231
prop
12321232
end

lib/debug/server_dap.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ def process_dap args
907907
case expr
908908
when /\A\@\S/
909909
begin
910-
result = M_INSTANCE_VARIABLE_GET.bind_call(b.receiver, expr)
910+
result = Reflection.instance_variable_get_from(b.receiver, expr)
911911
rescue NameError
912912
message = "Error: Not defined instance variable: #{expr.inspect}"
913913
end
@@ -929,8 +929,8 @@ def process_dap args
929929
result = b.local_variable_get(expr)
930930
rescue NameError
931931
# try to check method
932-
if M_RESPOND_TO_P.bind_call(b.receiver, expr, include_all: true)
933-
result = M_METHOD.bind_call(b.receiver, expr)
932+
if Reflection.responds_to?(b.receiver, expr, include_all: true)
933+
result = Reflection.method_of(b.receiver, expr)
934934
else
935935
message = "Error: Can not evaluate: #{expr.inspect}"
936936
end
@@ -1010,10 +1010,10 @@ def evaluate_result r
10101010
end
10111011

10121012
def type_name obj
1013-
klass = M_CLASS.bind_call(obj)
1013+
klass = Reflection.class_of(obj)
10141014

10151015
begin
1016-
M_NAME.bind_call(klass) || klass.to_s
1016+
Reflection.name_of(klass) || klass.to_s
10171017
rescue Exception => e
10181018
"<Error: #{e.message} (#{e.backtrace.first}>"
10191019
end
@@ -1027,7 +1027,7 @@ def variable_ name, obj, indexedVariables: 0, namedVariables: 0
10271027
vid = 0
10281028
end
10291029

1030-
namedVariables += M_INSTANCE_VARIABLES.bind_call(obj).size
1030+
namedVariables += Reflection.instance_variables_of(obj).size
10311031

10321032
if NaiveString === obj
10331033
str = obj.str.dump

lib/debug/session.rb

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2340,11 +2340,11 @@ def self.safe_inspect obj, max_length: SHORT_INSPECT_LENGTH, short: false
23402340
obj.inspect
23412341
end
23422342
rescue NoMethodError => e
2343-
klass, oid = M_CLASS.bind_call(obj), M_OBJECT_ID.bind_call(obj)
2343+
klass, oid = Reflection.class_of(obj), Reflection.object_id_of(obj)
23442344
if obj == (r = e.receiver)
23452345
"<\##{klass.name}#{oid} does not have \#inspect>"
23462346
else
2347-
rklass, roid = M_CLASS.bind_call(r), M_OBJECT_ID.bind_call(r)
2347+
rklass, roid = Reflection.class_of(r), Reflection.object_id_of(r)
23482348
"<\##{klass.name}:#{roid} contains <\##{rklass}:#{roid} and it does not have #inspect>"
23492349
end
23502350
rescue Exception => e
@@ -2623,12 +2623,3 @@ class Binding
26232623
alias break debugger
26242624
alias b debugger
26252625
end
2626-
2627-
# for Ruby 2.6 compatibility
2628-
unless method(:p).unbind.respond_to? :bind_call
2629-
class UnboundMethod
2630-
def bind_call(obj, *args)
2631-
self.bind(obj).call(*args)
2632-
end
2633-
end
2634-
end

lib/debug/thread_client.rb

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,6 @@
66
require_relative 'color'
77

88
module DEBUGGER__
9-
M_INSTANCE_VARIABLES = method(:instance_variables).unbind
10-
M_INSTANCE_VARIABLE_GET = method(:instance_variable_get).unbind
11-
M_CLASS = method(:class).unbind
12-
M_SINGLETON_CLASS = method(:singleton_class).unbind
13-
M_KIND_OF_P = method(:kind_of?).unbind
14-
M_RESPOND_TO_P = method(:respond_to?).unbind
15-
M_METHOD = method(:method).unbind
16-
M_OBJECT_ID = method(:object_id).unbind
17-
M_NAME = method(:name).unbind
18-
199
module SkipPathHelper
2010
def skip_path?(path)
2111
!path ||
@@ -590,8 +580,8 @@ def show_ivars pat, expr = nil
590580
end
591581

592582
if _self
593-
M_INSTANCE_VARIABLES.bind_call(_self).sort.each{|iv|
594-
value = M_INSTANCE_VARIABLE_GET.bind_call(_self, iv)
583+
Reflection.instance_variables_of(_self).sort.each{|iv|
584+
value = Reflection.instance_variable_get_from(_self, iv)
595585
puts_variable_info iv, value, pat
596586
}
597587
end
@@ -617,7 +607,7 @@ def get_consts expr = nil, only_self: false, &block
617607
rescue Exception => e
618608
# ignore
619609
else
620-
if M_KIND_OF_P.bind_call(_self, Module)
610+
if Reflection.is_kind_of(_self, Module)
621611
iter_consts _self, &block
622612
return
623613
else
@@ -626,10 +616,10 @@ def get_consts expr = nil, only_self: false, &block
626616
end
627617
elsif _self = current_frame&.self
628618
cs = {}
629-
if M_KIND_OF_P.bind_call(_self, Module)
619+
if Reflection.is_kind_of(_self, Module)
630620
cs[_self] = :self
631621
else
632-
_self = M_CLASS.bind_call(_self)
622+
_self = Reflection.class_of(_self)
633623
cs[_self] = :self unless only_self
634624
end
635625

@@ -769,20 +759,20 @@ def show_outline expr
769759

770760
locals = current_frame&.local_variables
771761

772-
klass = M_CLASS.bind_call(obj)
762+
klass = Reflection.class_of(obj)
773763
klass = obj if Class == klass || Module == klass
774764

775-
o.dump("constants", obj.constants) if M_RESPOND_TO_P.bind_call(obj, :constants)
765+
o.dump("constants", obj.constants) if Reflection.responds_to?(obj, :constants)
776766
outline_method(o, klass, obj)
777-
o.dump("instance variables", M_INSTANCE_VARIABLES.bind_call(obj))
767+
o.dump("instance variables", Reflection.instance_variables_of(obj))
778768
o.dump("class variables", klass.class_variables)
779769
o.dump("locals", locals.keys) if locals
780770
end
781771
end
782772

783773
def outline_method(o, klass, obj)
784774
begin
785-
singleton_class = M_SINGLETON_CLASS.bind_call(obj)
775+
singleton_class = Reflection.singleton_class_of(obj)
786776
rescue TypeError
787777
singleton_class = nil
788778
end
@@ -1192,7 +1182,7 @@ def wait_next_action_
11921182
obj_inspect = truncate(obj_inspect, width: width)
11931183
end
11941184

1195-
event! :result, :trace_pass, M_OBJECT_ID.bind_call(obj), obj_inspect, opt
1185+
event! :result, :trace_pass, Reflection.object_id_of(obj), obj_inspect, opt
11961186
rescue => e
11971187
puts e.message
11981188
event! :result, nil

lib/debug/tracer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ def setup
185185
@tracer = TracePoint.new(:a_call){|tp|
186186
next if skip?(tp)
187187

188-
if M_OBJECT_ID.bind_call(tp.self) == @obj_id
188+
if Reflection.object_id_of(tp.self) == @obj_id
189189
klass = tp.defined_class
190190
method = tp.method_id
191191
method_info =

test/debug/reflection_test.rb

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# frozen_string_literal: true
2+
3+
require 'test/unit'
4+
require_relative '../../lib/debug/reflection'
5+
6+
module DEBUGGER__
7+
class ReflectionTest < Test::Unit::TestCase
8+
def setup
9+
@sample_object = SampleClass.new(1, 2)
10+
end
11+
12+
def test_instance_variables_of
13+
assert_equal [:@a, :@b], Reflection.instance_variables_of(@sample_object)
14+
end
15+
16+
def test_instance_variables_get
17+
assert_equal 1, Reflection.instance_variable_get_from(@sample_object, :@a)
18+
assert_equal 2, Reflection.instance_variable_get_from(@sample_object, :@b)
19+
end
20+
21+
def test_class_of
22+
assert_same SampleClass, Reflection.class_of(@sample_object)
23+
end
24+
25+
def test_singleton_class_of
26+
expected = class << SampleClass
27+
self
28+
end
29+
30+
assert_same expected, Reflection.singleton_class_of(SampleClass)
31+
end
32+
33+
def test_is_kind_of?()
34+
assert_true Reflection.is_kind_of?(@sample_object, SampleClass)
35+
assert_false Reflection.is_kind_of?(@sample_object, Object)
36+
end
37+
38+
def test_responds_to?
39+
assert_true Reflection.responds_to?(@sample_object, :a)
40+
assert_false Reflection.responds_to?(@sample_object, :doesnt_exist)
41+
42+
assert_false Reflection.responds_to?(@sample_object, :sample_private_method)
43+
assert_false Reflection.responds_to?(@sample_object, :sample_private_method, include_all: false)
44+
assert_true Reflection.responds_to?(@sample_object, :sample_private_method, include_all: true)
45+
end
46+
47+
def test_method_of
48+
assert_equal 1, Reflection.method_of(@sample_object, :a).call
49+
end
50+
51+
def test_object_id_of
52+
assert_equal @sample_object.__id__, Reflection.object_id_of(@sample_object)
53+
end
54+
55+
def test_name_of
56+
assert_equal "DEBUGGER__::ReflectionTest::SampleClass", Reflection.name_of(SampleClass)
57+
end
58+
59+
def test_bind_call_backport
60+
omit_if(
61+
UnboundMethod.instance_method(:bind_call).source_location.nil?,
62+
"This Ruby version (#{RUBY_VERSION}) has a native #bind_call implementation, so it doesn't need the backport.",
63+
)
64+
65+
puts caller_locations
66+
original_object = SampleTarget.new("original")
67+
new_target = SampleTarget.new("new")
68+
69+
m = original_object.method(:sample_method).unbind
70+
71+
rest_args = ["a1", "a2"]
72+
kwargs = { k1: 1, k2: 2 }
73+
proc = Proc.new { |x| x }
74+
75+
result = m.bind_call(new_target, "parg1", "parg2", *rest_args, **kwargs, &proc)
76+
77+
assert_equal "new", result.fetch(:self).id
78+
assert_equal "parg1", result.fetch(:parg1)
79+
assert_equal "parg2", result.fetch(:parg2)
80+
assert_equal rest_args, result.fetch(:rest_args)
81+
assert_equal kwargs, result.fetch(:kwargs)
82+
assert_same proc, result.fetch(:block)
83+
end
84+
85+
private
86+
87+
# A class for testing reflection, which doesn't implement all the usual reflection methods being tested.
88+
class SampleClass < BasicObject
89+
attr_reader :a, :b
90+
91+
def initialize(a, b)
92+
@a = a
93+
@b = b
94+
end
95+
96+
private
97+
98+
def sample_private_method; end
99+
100+
class << self
101+
undef_method :method
102+
end
103+
end
104+
105+
class SampleTarget
106+
attr_reader :id
107+
108+
def initialize(id)
109+
@id = id
110+
end
111+
112+
def sample_method(parg1, parg2, *rest_args, **kwargs, &block)
113+
{ self: self, parg1: parg1, parg2: parg2, rest_args: rest_args, kwargs: kwargs, block: block }
114+
end
115+
end
116+
end
117+
end

0 commit comments

Comments
 (0)