-
Notifications
You must be signed in to change notification settings - Fork 142
Fix REPL binding #424
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix REPL binding #424
Changes from all commits
494ba34
91a8061
205004a
2fb884b
84e3fab
27752e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -344,29 +344,43 @@ def instance_eval_for_cmethod frame_self, src | |
| frame_self.instance_eval(src) | ||
| end | ||
|
|
||
| def eval_binding | ||
| current_frame&.binding || TOPLEVEL_BINDING | ||
| end | ||
|
|
||
| SPECIAL_LOCAL_VARS = [ | ||
| [:raised_exception, "_raised"], | ||
| [:return_value, "_return"], | ||
| ] | ||
| SPECIAL_LOCAL_VAR_NAMES = SPECIAL_LOCAL_VARS.map { |_, n| n } | ||
|
|
||
| def frame_eval src, re_raise: false | ||
| @success_last_eval = false | ||
|
|
||
| b = current_frame&.eval_binding || TOPLEVEL_BINDING | ||
| b = b.dup | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For some unknown reasons, I can't reproduce the polluted binding case anymore 🙈 . So I just remove the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, it's because the current implementation only defines them at the frame that has those values. So it's not easy or even impossible to leak them into the program. For example, the def foo
# ...
end # <= hereAnd theoretically no user program will be able to access it, so there's no need to worry about it polluting the program.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. without |
||
| # the binding of original program | ||
| original_binding = eval_binding | ||
| # the binding used by debugger REPL that can have special locals | ||
| repl_binding = eval_binding.dup | ||
|
|
||
| special_local_variables current_frame do |name, var| | ||
| b.local_variable_set(name, var) if /\%/ !~ name | ||
| special_local_variables current_frame, binding: repl_binding do |name, var| | ||
| repl_binding.local_variable_set(name, var) if /\%/ !~ name | ||
| end | ||
|
|
||
| result = if b | ||
| f, _l = b.source_location | ||
| b.eval(src, "(rdbg)/#{f}") | ||
| result = if repl_binding | ||
| f, _l = repl_binding.source_location | ||
| repl_binding.eval(src, "(rdbg)/#{f}") | ||
| else | ||
| frame_self = current_frame.self | ||
| instance_eval_for_cmethod(frame_self, src) | ||
| end | ||
| @success_last_eval = true | ||
|
|
||
| # write non-special local variables back to the original binding | ||
| repl_binding.local_variables.each do |var| | ||
| next if SPECIAL_LOCAL_VAR_NAMES.include?(var) | ||
| original_binding.local_variable_set(var, repl_binding.local_variable_get(var)) | ||
| end | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After we dup the original binding, local variables changes made in the REPL won't be applied to it. So we need to manually propagate the values back to the original binding.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. on master, it is propagated. which case do you considering?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe you mean: but
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. byebug's example. [master]$ byebug target.rb
[1, 10] in /mnt/c/ko1/src/rb/ruby-debug/target.rb
1:
=> 2: a = 1
3: b = 2
4:
5: __END__
6:
7: require 'foo'
8:
9: __END__
10:
(byebug) y = 10
10
(byebug) p y
10
10
(byebug) n
[1, 10] in /mnt/c/ko1/src/rb/ruby-debug/target.rb
1:
2: a = 1
=> 3: b = 2
4:
5: __END__
6:
7: require 'foo'
8:
9: __END__
10:
(byebug) y
*** NoMethodError Exception: undefined method `end_line=' for nil:NilClass |
||
|
|
||
| result | ||
|
|
||
| rescue Exception => e | ||
|
|
@@ -442,10 +456,12 @@ def current_frame | |
|
|
||
| ## cmd: show | ||
|
|
||
| def special_local_variables frame | ||
| def special_local_variables frame, binding: nil | ||
| SPECIAL_LOCAL_VARS.each do |mid, name| | ||
| next unless frame&.send("has_#{mid}") | ||
| name = name.sub('_', '%') if frame.binding.local_variable_defined? name | ||
| binding ||= frame&.binding | ||
| next unless binding | ||
| name = name.sub('_', '%') if binding.local_variable_defined? name | ||
| yield name, frame.send(mid) | ||
| end | ||
| end | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't correct as
local_variablesonly yields keys but no values. But the fact that we didn't spot this probably means it's never called anyway.