-
Notifications
You must be signed in to change notification settings - Fork 127
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 incorrect #bind_call
monkey patch
#1002
base: master
Are you sure you want to change the base?
Conversation
3d0eebb
to
0213f32
Compare
module_function def instance_variables_of(o) | ||
M_INSTANCE_VARIABLES.bind_call(o) | ||
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.
Connecting the debugger to a Ruby version pre-2.7 will have the effect of back-porting the bind_call
method, which would otherwise exist.
This is convenient for the implementation of the Ruby/debug
itself, but perhaps it's not a good idea, as developers' code can call bind_call
successfully, but only when they're debugging, which could be quite confusing.
Although it's more tedious, perhaps it's a better idea to conditionally define one of two implementations of these helpers, rather than polluting UnboundMethod
? E.g.
module_function def instance_variables_of(o) | |
M_INSTANCE_VARIABLES.bind_call(o) | |
end | |
if UnboundMethod.method_defined?(:bind_call) | |
module_function def instance_variables_of(o) | |
M_INSTANCE_VARIABLES.bind_call(o) | |
end | |
else | |
module_function def instance_variables_of(o) | |
M_INSTANCE_VARIABLES.bind(o).call | |
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.
I'm not sure we should support pre-2.7 though...
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.
If we don't want to support pre-2.7, then we should remove the monkey patch entirely.
I don't personally use anything pre-3.2, but it feels too soon to drop 2.7. In any case, the current implementation of it is incorrect, and we should not leave it as-is.
@@ -1059,8 +1059,8 @@ def process_cdp args | |||
result = b.local_variable_get(expr) | |||
rescue NameError | |||
# try to check method | |||
if M_RESPOND_TO_P.bind_call(b.receiver, expr, include_all: true) |
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.
Interestingly, respond_to?
's include_all
argument is positional, not keyword (notice =
, not :
in the docs):
respond_to?(symbol, include_all=false) → true or false
The way this is called causes an implicit Hash to be passed positionally, as if it was:
if M_RESPOND_TO_P.bind_call(b.receiver, expr, { include_all: true })
This would cause include_all
's value to be literally the Hash { include_all: true }
. It's truthy, so it behaves equivalent to true
, so it happens to be accidentally correct. This wouldn't work though:
if M_RESPOND_TO_P.bind_call(b.receiver, expr, include_all: false)
Since the { include_all: false }
Hash would still be truthy.
I made this a kwarg in the Reflection helper, so it's more clear.
omit_if( | ||
UnboundMethod.instance_method(:bind_call).source_location.nil?, | ||
"This Ruby version (#{RUBY_VERSION}) has a native #bind_call implementation, so it doesn't need the backport.", | ||
) |
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 causes a fair bit of noise in the test output:
=================================================================================================================================================
Omission: This Ruby version (3.2.2) has a native #bind_call implementation, so it doesn't need the backport. [test_bind_call_backport(DEBUGGER__::ReflectionTest)]
./test/debug/reflection_test.rb:60:in `test_bind_call_backport'
=================================================================================================================================================
Finished in 0.001056 seconds.
-------------------------------------------------------------------------------------------------------------------------------------------------
10 tests, 15 assertions, 0 failures, 0 errors, 0 pendings, 1 omissions, 0 notifications
Perhaps it would be better to just wrap the def test_bind_call_backport
in a big if
statement?
assert_equal "parg2", result.fetch(:parg2) | ||
assert_equal rest_args, result.fetch(:rest_args) | ||
assert_equal kwargs, result.fetch(:kwargs) | ||
assert_same proc, result.fetch(:block) |
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.
The buggy patch would cause this to be nil
, but it now works correctly.
The name "Reflection" is not clear for me. Reflection is important programming technique and debugger should support it especially on Ruby. However, this module doesn't represent this kind of general idea. |
@ko1 I think Reflection is exactly the right term. Have a look at the Wikiepdia page:
|
Yes, this module uses "Reflection" feature, but this module doesn't represent the feature of "Reflection". |
@ko1 Sorry, I don't think I understand. I suppose This naming also what Tapioca calls it. If you would like an alternative name, could you suggest one? I don't have any better ideas. |
|
||
module DEBUGGER__ | ||
module Reflection | ||
module_function def instance_variables_of(o) |
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.
why do you use module_function
?
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.
If the module were included, it could override kernel methods like #responds_to?
, which wouldn't be desirable. So these methods should be called directly on the module.
Would you prefer I use include self
instead?
This module provides a feature to call essential Ruby methods so
and so on...? |
Personally, I think those are too generic.
The answer is: "the reflection-related features" I really do think |
I understand the misunderstanding with us.
Original methods mean that they are guaranteed to provide original feature even if they are re-defined by other classes. For example, if we get trouble with Hmm. |
Hi Koichi! You're correct, I think this is exactly the misunderstanding.
Correct! I was thinking of it more by purpose ("reflection related features") than implementation detail ("it's an original Ruby method, ignoring redefinition"). If I wanted the original I'm okay with either I approach. Please feel free to merge whichever you prefer. |
I also misunderstand that you introduced different naming like module_function def instance_variable_get_from(o, name)
M_INSTANCE_VARIABLE_GET.bind_call(o, name)
end (not |
OK, How about @mametter told me that here we need the ability for "Introspection" |
Description
Ruby/debug
monkey patches in an implementation ofUnboundMethod#bind_call
for compatibility with Rubies older than 2.7. Unfortunately, there is a bug in the implementation, in that it doesn't pass the block parameter correctly. Here's a demo:bind_call_bug_demo.rb
This repo introduces a corrected patch, and with a test that confirms it:
I also introduces a new
Reflection
module which extracts all thebind_call
-ed reflection methods, so they're more readable. It also adds tests for them.