debug
debug copied to clipboard
Loading `debug` gem breaks `blankslate` gem
Your environment
ruby -v: ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]rdbg -v: rdbg 1.6.2
Describe the bug
The debug gem undefines and redefines singleton_method_added.
The blankslate gem provides a "abstract base class with no predefined methods" except for some core Ruby builtins. "BlankSlate is useful as a base class when writing classes that depend upon method_missing (e.g. dynamic proxies)", so it only works if singleton_method_added is available. However, it hides singleton_method_added because it's redefined by the debug gem.
This problem does not occur when the debug gem is not required.
To Reproduce
require 'debug'
require 'blankslate'
BlankSlate.new.singleton_method_added
Buggy output -- the method isn't defined:
Traceback (most recent call last):
repro.rb:4:in `<main>': undefined method `singleton_method_added' for #<BlankSlate:0x000055a654b12fb0> (NoMethodError)
Expected behavior
The method is defined, but private:
Traceback (most recent call last):
repro.rb:3:in `<main>': private method `singleton_method_added' called for #<BlankSlate:0x000055d758dcd540> (NoMethodError)
Workaround
We can work around the bug by explicilty unhiding this method, or by requiring blankslate before debug:
BlankSlate.reveal(:singleton_method_added)
Additional context
The problem is caused by this code in the debug gem:
https://github.com/ruby/debug/blob/19b4dde3308f532943e4234d1588d4fa26c52345/lib/debug/session.rb#L1686-L1689
Why does the debug gem redefine this method?
This problem breaks one of Stripe's services in development because we start a debug session when it starts up, before it requires the blankslate gem. We are working around the issue, but it would be great if it could be fixed!
Thank you for the report.
I didn't know the blankslate but could you make a smaller repro-code without the blankslate gem?
Otherwise I'll try to make it.
minitest also breaks, see https://github.com/minitest/minitest/issues/929
Here is blankslate torn all the way down to a minimal repro:
#!/usr/bin/env ruby -vw
class BlankSlate
def self.hide(name)
return unless instance_methods.include? name
undef_method name
end
hide :singleton_method_added
end
begin
BlankSlate.new.singleton_method_added
rescue NoMethodError => e
p e.message.include?("private method") ? :GOOD : :BAD
else
p :BAD
end
__END__
% ruby -rdebug debug_blankslate.rb
:BAD
% ruby debug_blankslate.rb
:GOOD
run w/o ruby -rdebug to output GOOD and run with ruby -rdebug to output BAD...
It essentially boils down to "is singleton_method_added defined?"
for minitest/mock, not too dissimilar:
#!/usr/bin/env ruby -vw
class Mock
overridden_methods = %w[
===
class
inspect
instance_eval
instance_variables
object_id
public_send
respond_to_missing?
send
to_s
]
instance_methods.each do |m|
undef_method m unless overridden_methods.include?(m.to_s) || m =~ /^__/
end
def method_missing sym, *args, **kwargs, &block # :nodoc:
raise NoMethodError, "unmocked method %p" % [sym]
end
end
foo = Mock.new
def foo.bar = 'very awesome foo'
p :GOOD
I think in my case I'm going to add singleton_method_added to overridden_methods if DEBUGGER__ is defined.