debug
debug copied to clipboard
Fix incorrect `#bind_call` monkey patch
Description
Ruby/debug monkey patches in an implementation of UnboundMethod#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:
$ chruby 2.6.10 && ruby ./bind_call_bug_demo.rb
Backporting UnboundMethod#bind_call in Ruby 2.6.10.
Incorrect result from buggy bind_call: nil
Correct result from fixed bind_call: #<Proc:0x00000001100df230@./bind_call_bug_demo.rb:12>
Expected proc: #<Proc:0x00000001100df230@./bind_call_bug_demo.rb:12>
$ chruby 3.2.2 && ruby ./bind_call_bug_demo.rb
Ruby 3.2.2 already has UnboundMethod#bind_call natively defined.
Correct result from native bind_call: #<Proc:0x00000001055ab5f8 ./bind_call_bug_demo.rb:12>
Expected proc: #<Proc:0x00000001055ab5f8 ./bind_call_bug_demo.rb:12>
bind_call_bug_demo.rb
require "test/unit"
class SampleTarget < Struct.new(:id)
def pass_block_through(&block)
block
end
end
target_object = SampleTarget.new
m = SampleTarget.instance_method(:pass_block_through)
proc = Proc.new { |x| x }
if UnboundMethod.method_defined?(:bind_call)
puts "Ruby #{RUBY_VERSION} already has UnboundMethod#bind_call natively defined.\n\n"
puts "Correct result from native bind_call: #{m.bind_call(target_object, &proc).inspect}" # => #<Proc:...>
puts " Expected proc: #{proc}" # => #<Proc:...>
else
puts "Backporting UnboundMethod#bind_call in Ruby #{RUBY_VERSION}.\n\n"
class UnboundMethod
def bind_call(receiver, *args)
bind(receiver).call(*args)
end
def bind_call_fixed(receiver, *args, &block)
bind(receiver).call(*args, &block)
end
end
puts "Incorrect result from buggy bind_call: #{m.bind_call(target_object, &proc).inspect}" # => nil
puts " Correct result from fixed bind_call: #{m.bind_call_fixed(target_object, &proc).inspect}" # => #<Proc:...>
puts " Expected proc: #{proc}" # => #<Proc:...>
end
This repo introduces a corrected patch, and with a test that confirms it:
# for Ruby 2.6 compatibility
unless UnboundMethod.method_defined?(:bind_call)
class UnboundMethod
def bind_call(receiver, *args, &block)
bind(receiver).call(*args, &block)
end
end
end
I also introduces a new Reflection module which extracts all the bind_call-ed reflection methods, so they're more readable. It also adds tests for them.
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. Could you make another word?
@ko1 I think Reflection is exactly the right term. Have a look at the Wikiepdia page:
- Discover and modify source-code constructions (such as code blocks, classes, methods, protocols, etc.) as first-class objects at runtime.
- Convert a string matching the symbolic name of a class or function into a reference to or invocation of that class or function.
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 ReflectionHelpers/ReflectionUtils would be more precise names, but *Helpers names are smelly. It reads better at the callsite, e.g. the standard library gives you Math.sin not MathHelpers.sin.
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.
If you would like an alternative name, could you suggest one? I don't have any better ideas.
This module provides a feature to call essential Ruby methods so
EssentialRubyFeaturePrimitiveRubyFeatureOriginalRubyFeature
and so on...?
Personally, I think those are too generic.
___RubyFeature raises the question: "Which Ruby features are essential, primitive or original?"
The answer is: "the reflection-related features"
I really do think Reflection is the exact right term for this, but it's more important to me that this lands in any form. Please feel free to use whichever you find best.
___RubyFeature raises the question: "Which Ruby features are essential, primitive or original?" The answer is: "the reflection-related features"
I understand the misunderstanding with us.
- You understand these methods are "reflection related features"
- I understand these methods are guaranteed to be original methods.
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 Array#each redefinition, I think Array.instance_method(:each) should be in this module. But maybe you don't think so.
Hmm.
Hi Koichi! You're correct, I think this is exactly the misunderstanding.
For example, if we get trouble with Array#each redefinition, I think Array.instance_method(:each) should be in this module. But maybe you don't think so.
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 Array#each, I would put it in some new array-specific module which better represents some relevant high-level idea (e.g. “Enumeration”)
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 instance_variable_get).
OK, How about ObjectIntrospector?
@mametter told me that https://stackoverflow.com/questions/25198271/what-is-the-difference-between-introspection-and-reflection#answer-41161878
here we need the ability for "Introspection"