Skip to content

Commit 0a0f2e6

Browse files
committed
Use same dupped binding while same sub-session
This is another fix of #424. Tests are imported from #424 written by @st0012.
1 parent dbbee39 commit 0a0f2e6

File tree

3 files changed

+130
-60
lines changed

3 files changed

+130
-60
lines changed

lib/debug/frame_info.rb

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ module DEBUGGER__
55
:has_return_value, :return_value,
66
:has_raised_exception, :raised_exception,
77
:show_line,
8-
:_local_variables, :_callee # for recorder
8+
:_local_variables, :_callee, # for recorder
9+
:dupped_binding,
910
)
1011

1112
# extend FrameInfo with debug.so
@@ -118,19 +119,13 @@ def location_str
118119
"#{pretty_path}:#{location.lineno}"
119120
end
120121

121-
private def make_binding
122-
__newb__ = self.self.instance_eval('binding')
123-
self.local_variables.each{|var, val|
124-
__newb__.local_variable_set(var, val)
125-
}
126-
__newb__
127-
end
128-
129122
def eval_binding
130-
if b = self.binding
123+
if b = self.dupped_binding
131124
b
132-
elsif self.local_variables
133-
make_binding
125+
else
126+
b = TOPLEVEL_BINDING unless b = self.binding
127+
b = self.binding || TOPLEVEL_BINDING
128+
self.dupped_binding = b.dup
134129
end
135130
end
136131

lib/debug/thread_client.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,7 @@ def instance_eval_for_cmethod frame_self, src
352352
def frame_eval src, re_raise: false
353353
@success_last_eval = false
354354

355-
b = current_frame&.eval_binding || TOPLEVEL_BINDING
356-
b = b.dup
355+
b = current_frame.eval_binding
357356

358357
special_local_variables current_frame do |name, var|
359358
b.local_variable_set(name, var) if /\%/ !~ name
@@ -445,7 +444,7 @@ def current_frame
445444
def special_local_variables frame
446445
SPECIAL_LOCAL_VARS.each do |mid, name|
447446
next unless frame&.send("has_#{mid}")
448-
name = name.sub('_', '%') if frame.binding.local_variable_defined? name
447+
name = name.sub('_', '%') if frame.eval_binding.local_variable_defined?(name)
449448
yield name, frame.send(mid)
450449
end
451450
end
@@ -653,7 +652,7 @@ def make_breakpoint args
653652
case args.first
654653
when :method
655654
klass_name, op, method_name, cond, cmd, path = args[1..]
656-
bp = MethodBreakpoint.new(current_frame.binding, klass_name, op, method_name, cond: cond, command: cmd, path: path)
655+
bp = MethodBreakpoint.new(current_frame.eval_binding, klass_name, op, method_name, cond: cond, command: cmd, path: path)
657656
begin
658657
bp.enable
659658
rescue Exception => e

test/debug/debugger_local_test.rb

Lines changed: 120 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,84 +4,160 @@
44

55
module DEBUGGER__
66
class DebuggerLocalsTest < TestCase
7-
class RaisedTest < TestCase
7+
class REPLLocalsTest < TestCase
88
def program
99
<<~RUBY
10-
1| _rescued_ = 1111
11-
2| foo rescue nil
12-
3|
13-
4| # check repl variable doesn't leak to the program
14-
5| result = _rescued_ * 2
15-
6| binding.b
10+
1| a = 1
1611
RUBY
1712
end
1813

19-
def test_raised_is_accessible_from_repl
14+
def test_locals_added_in_locals_are_accessible_between_evaluations
2015
debug_code(program) do
21-
type "catch Exception"
22-
type "c"
23-
type "_raised"
24-
assert_line_text(/#<NameError: undefined local variable or method `foo' for main:Object/)
25-
type "c"
26-
type "result"
27-
assert_line_text(/2222/)
16+
type "y = 50"
17+
type "y"
18+
assert_line_text(/50/)
2819
type "c"
2920
end
3021
end
22+
end
3123

32-
def test_raised_is_accessible_from_command
33-
debug_code(program) do
34-
type "catch Exception pre: p _raised"
35-
type "c"
36-
assert_line_text(/#<NameError: undefined local variable or method `foo' for main:Object/)
37-
type "c"
38-
type "result"
39-
assert_line_text(/2222/)
40-
type "c"
24+
class RaisedTest < TestCase
25+
class RubyMethodTest < TestCase
26+
def program
27+
<<~RUBY
28+
1| foo rescue nil
29+
RUBY
30+
end
31+
32+
def test_raised_is_accessible_from_repl
33+
debug_code(program) do
34+
type "catch Exception"
35+
type "c"
36+
type "_raised"
37+
assert_line_text(/#<NameError: undefined local variable or method `foo' for main:Object/)
38+
type "c"
39+
end
40+
end
41+
42+
def test_raised_is_accessible_from_command
43+
debug_code(program) do
44+
type "catch Exception pre: p _raised"
45+
type "c"
46+
assert_line_text(/#<NameError: undefined local variable or method `foo' for main:Object/)
47+
type "c"
48+
end
49+
end
50+
end
51+
52+
class CMethodTest < TestCase
53+
def program
54+
<<~RUBY
55+
1| 1/0 rescue nil
56+
RUBY
57+
end
58+
59+
def test_raised_is_accessible_from_repl
60+
debug_code(program) do
61+
type "catch Exception"
62+
type "c"
63+
type "_raised"
64+
assert_line_text(/ZeroDivisionError/)
65+
type "c"
66+
end
67+
end
68+
69+
def test_raised_is_accessible_from_command
70+
debug_code(program) do
71+
type "catch Exception pre: p _raised"
72+
type "c"
73+
assert_line_text(/ZeroDivisionError/)
74+
type "c"
75+
end
76+
end
77+
end
78+
79+
class EncapsulationTest < TestCase
80+
def program
81+
<<~RUBY
82+
1| 1/0 rescue nil
83+
2| a = _raised
84+
RUBY
85+
end
86+
87+
def test_raised_doesnt_leak_to_program_binding
88+
debug_code(program) do
89+
type "catch StandardError"
90+
type "c"
91+
# stops for ZeroDivisionError
92+
type "info"
93+
type "_raised"
94+
assert_line_text(/ZeroDivisionError/)
95+
type "c"
96+
97+
# stops for NoMethodError because _raised is not defiend in the program
98+
type "_raised"
99+
assert_line_text(/NameError: undefined local variable or method `_raised' for main:Object/)
100+
type "c"
101+
end
41102
end
42103
end
43104
end
44105

45106
class ReturnedTest < TestCase
46107
def program
47108
<<~RUBY
48-
1| _return = 1111
49-
2|
50-
3| def foo
51-
4| "foo"
52-
5| end
53-
6|
54-
7| foo
55-
8|
56-
9| # check repl variable doesn't leak to the program
57-
10| result = _return * 2
58-
11|
59-
12| binding.b
109+
1| def foo
110+
2| "foo"
111+
3| end
112+
4|
113+
5| foo
60114
RUBY
61115
end
62116

63117
def test_returned_is_accessible_from_repl
64118
debug_code(program) do
65-
type "b 5"
119+
type "b 3"
66120
type "c"
67121
type "_return + 'bar'"
68122
assert_line_text(/"foobar"/)
69123
type "c"
70-
type "result"
71-
assert_line_text(/2222/)
72-
type "q!"
73124
end
74125
end
75126

76127
def test_returned_is_accessible_from_command
77128
debug_code(program) do
78-
type "b 5 pre: p _return + 'bar'"
129+
type "b 3 pre: p _return + 'bar'"
79130
type "c"
80131
assert_line_text(/"foobar"/)
81132
type "c"
82-
type "result"
83-
assert_line_text(/2222/)
84-
type "q!"
133+
end
134+
end
135+
136+
class EncapsulationTest < TestCase
137+
def program
138+
<<~RUBY
139+
1| def foo
140+
2| "foo"
141+
3| end
142+
4|
143+
5| foo
144+
6| puts _return
145+
RUBY
146+
end
147+
148+
def test_raised_doesnt_leak_to_program_binding
149+
debug_code(program) do
150+
type "catch StandardError"
151+
type "b 3"
152+
type "c"
153+
type "_return + 'bar'"
154+
assert_line_text(/"foobar"/)
155+
type "c"
156+
# stops for NoMethodError because _return is not defiend in the program
157+
type "_raised"
158+
assert_line_text(/NameError: undefined local variable or method `_return' for main:Object/)
159+
type "c"
160+
end
85161
end
86162
end
87163
end

0 commit comments

Comments
 (0)