Project

General

Profile

Actions

Bug #17519

closed

set_visibility fails when a prepended module and a refinement both exist

Bug #17519: set_visibility fails when a prepended module and a refinement both exist

Added by fledman (David Feldman) almost 5 years ago. Updated over 4 years ago.

Status:
Closed
Assignee:
-
Target version:
-
ruby -v:
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin19]
[ruby-core:101981]

Description

the set_visibility functions (aka public/private/protected) fail with NameError when:

  • called on a specific object's singleton class
  • for a specific method
  • and both of the following are true
  • any module has been prepended to the object's class
  • a refinement exists for the specific method

note that the refinement does not need to ever be used

I have reproduced this on 3.0.0, 2.7.2, and 2.6.6 (those were the only 3 version I tested)

def test_visibility(function) h1 = {x:1, y:1} h2 = {x:2, y:2} h1.singleton_class.send(:private, function) h2.singleton_class.send(:public, function) begin puts h1.public_send(function, :x) rescue NoMethodError => err puts "hit NoMethodError as expected: #{err.inspect}" end puts h2.public_send(function, :x) end 

succeeds without any prepended modules or refinements:

irb> test_visibility :except hit NoMethodError as expected: #<NoMethodError: private method `except' called for {:x=>1, :y=>1}:Hash> {:y=>2} => nil 

succeeds with only a prepended module:

irb> module Nothing; end; Hash.prepend(Nothing); Hash.ancestors => [Nothing, Hash, Enumerable, Object, PP::ObjectMixin, Kernel, BasicObject] irb> test_visibility :except hit NoMethodError as expected: #<NoMethodError: private method `except' called for {:x=>1, :y=>1}:Hash> {:y=>2} => nil 

succeeds with only a refinement:

irb> module NeverUsed refine Hash do def except(*keys) {never: 'used'} end end end => #<refinement:Hash@NeverUsed> irb> test_visibility :except hit NoMethodError as expected: #<NoMethodError: private method `except' called for {:x=>1, :y=>1}:Hash> {:y=>2} => nil 

fails with both a refinement and a prepended module:

irb> module Nothing; end; Hash.prepend(Nothing); Hash.ancestors => [Nothing, Hash, Enumerable, Object, PP::ObjectMixin, Kernel, BasicObject] irb> module NeverUsed refine Hash do def except(*keys) {never: 'used'} end end end => #<refinement:Hash@NeverUsed> irb> test_visibility :except Traceback (most recent call last): 6: from .rubies/ruby-3.0.0/bin/irb:23:in `<main>' 5: from .rubies/ruby-3.0.0/bin/irb:23:in `load' 4: from .rubies/ruby-3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.0/exe/irb:11:in `<top (required)>' 3: from (irb):25:in `<main>' 2: from (irb):5:in `test_visibility' 1: from (irb):5:in `private' NameError (undefined method `except' for class `#<Class:#<Hash:0x00007ffd6b176f18>>') Did you mean? exec # non-refined method still works irb> test_visibility :fetch hit NoMethodError as expected: #<NoMethodError: private method `fetch' called for {:x=>1, :y=>1}:Hash> 2 => nil 

Updated by fledman (David Feldman) almost 5 years ago Actions #1 [ruby-core:101982]

since :except is only available natively on Ruby3, here is a Ruby2 demonstration:

irb> RUBY_VERSION => "2.7.2" irb> module Nothing; end; Hash.prepend(Nothing); Hash.ancestors => [Nothing, Hash, Enumerable, Object, PP::ObjectMixin, Kernel, BasicObject] irb> module NeverUsed refine Hash do def fetch(key) 42 end end end => #<refinement:Hash@NeverUsed> irb> test_visibility :fetch Traceback (most recent call last): 6: from /Users/davidfeldman/.rubies/ruby-2.7.2/bin/irb:23:in `<main>' 5: from /Users/davidfeldman/.rubies/ruby-2.7.2/bin/irb:23:in `load' 4: from /Users/davidfeldman/.rubies/ruby-2.7.2/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>' 3: from (irb):25 2: from (irb):14:in `test_visibility' 1: from (irb):14:in `private' NameError (undefined method `fetch` for class `#<Class:#<Hash:0x00007fcdf3ac7ec0>>') # non-refined method still works irb> test_visibility :[] hit NoMethodError as expected: #<NoMethodError: private method `[]` called for {:x=>1, :y=>1}:Hash Did you mean? []=> 2 => nil 

Updated by fledman (David Feldman) almost 5 years ago Actions #2 [ruby-core:101983]

and a demonstration with a custom class:

RUBY_VERSION class Thing def direction 'LEFT' end end def test_thing_visibility t1 = Thing.new t2 = Thing.new t1.singleton_class.send(:private, :direction) t2.singleton_class.send(:public, :direction) begin puts t1.direction rescue NoMethodError => err puts "hit NoMethodError as expected: #{err.inspect}" end puts t2.direction end test_thing_visibility module NeverUsed refine Thing do def direction 'UP' end end end test_thing_visibility module Nothing; end; Thing.prepend(Nothing); Thing.ancestors test_thing_visibility 

outputs:

=> "2.6.6" => :direction => :test_thing_visibility hit NoMethodError as expected: #<NoMethodError: private method `direction' called for #<Thing:0x00007f8c6f871ce0>> LEFT => nil => #<refinement:Thing@NeverUsed> hit NoMethodError as expected: #<NoMethodError: private method `direction' called for #<Thing:0x00007f8c6f8b4a68>> LEFT => nil => [Nothing, Thing, Object, PP::ObjectMixin, Kernel, BasicObject] Traceback (most recent call last): 6: from /Users/davidfeldman/.rubies/ruby-2.6.6/bin/irb:23:in `<main>' 5: from /Users/davidfeldman/.rubies/ruby-2.6.6/bin/irb:23:in `load' 4: from /Users/davidfeldman/.rubies/ruby-2.6.6/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>' 3: from (irb):37 2: from (irb):11:in `test_thing_visibility' 1: from (irb):11:in `private' NameError (undefined method `direction' for class `#<Class:#<Thing:0x00007f8c6f8dcd10>>') 

Updated by fledman (David Feldman) almost 5 years ago Actions #3 [ruby-core:102004]

although the title makes it sound obscure, this bug is actually fairly easy to trigger when using rspec and rails:

  • activesupport >= 5 prepends a module onto Hash
  • i18n >= 1.3 refines several methods on Hash
  • power_assert (used by test-unit and minitest) refines most of the basic operators of the core classes

e.g. try to expect(some_hash).to receive(:except)

Updated by jeremyevans0 (Jeremy Evans) over 4 years ago Actions #4

I've added a pull request to fix this: https://github.com/ruby/ruby/pull/4200

Updated by jeremyevans (Jeremy Evans) over 4 years ago Actions #5

  • Status changed from Open to Closed

Applied in changeset git|58660e943488778563b9e41005a601e9660ce21f.


Skip refined method when exporting methods with changed visibility

Previously, attempting to change the visibility of a method in a
singleton class for a class/module that is prepended to and refined
would raise a NoMethodError.

Fixes [Bug #17519]

Updated by fledman (David Feldman) over 4 years ago Actions #6 [ruby-core:102888]

do you plan to backport the fix to any of the supported 2.x versions?

Updated by jeremyevans0 (Jeremy Evans) over 4 years ago Actions #7 [ruby-core:102889]

  • Backport changed from 2.5: UNKNOWN, 2.6: UNKNOWN, 2.7: UNKNOWN, 3.0: UNKNOWN to 2.5: UNKNOWN, 2.6: REQUIRED, 2.7: REQUIRED, 3.0: REQUIRED

fledman (David Feldman) wrote in #note-6:

do you plan to backport the fix to any of the supported 2.x versions?

The choice of which patches to backport is up to the branch maintainer. I've marked this for backporting, but it is their decision.

Updated by nagachika (Tomoyuki Chikanaga) over 4 years ago Actions #8 [ruby-core:102956]

  • Backport changed from 2.5: UNKNOWN, 2.6: REQUIRED, 2.7: REQUIRED, 3.0: REQUIRED to 2.5: UNKNOWN, 2.6: REQUIRED, 2.7: DONE, 3.0: REQUIRED

ruby_2_7 6e962f02b266c3a6c47e50cf2e9ab7b1db25e515 merged revision(s) 58660e943488778563b9e41005a601e9660ce21f.

Updated by naruse (Yui NARUSE) over 4 years ago Actions #9 [ruby-core:103169]

  • Backport changed from 2.5: UNKNOWN, 2.6: REQUIRED, 2.7: DONE, 3.0: REQUIRED to 2.5: UNKNOWN, 2.6: REQUIRED, 2.7: DONE, 3.0: DONE

ruby_3_0 d1cec0bca588266b9af1d55e592016c45ee68fbb merged revision(s) 58660e943488778563b9e41005a601e9660ce21f.

Updated by fledman (David Feldman) over 4 years ago Actions #10 [ruby-core:103244]

this fix seems to have introduced a new but similar bug: https://github.com/ruby/ruby/pull/4200#issuecomment-813671308

Updated by jeremyevans0 (Jeremy Evans) over 4 years ago Actions #11 [ruby-core:103245]

fledman (David Feldman) wrote in #note-10:

this fix seems to have introduced a new but similar bug: https://github.com/ruby/ruby/pull/4200#issuecomment-813671308

I can confirm the issue. Here's a pull request that fixes it: https://github.com/ruby/ruby/pull/4357

Updated by jeremyevans0 (Jeremy Evans) over 4 years ago Actions #12

  • Status changed from Closed to Open

Updated by jeremyevans (Jeremy Evans) over 4 years ago Actions #13

  • Status changed from Open to Closed

Applied in changeset git|4b36a597f48c857aa5eb9ed80fec0d02f6284646.


Fix setting method visibility for a refinement without an origin class

If a class has been refined but does not have an origin class,
there is a single method entry marked with VM_METHOD_TYPE_REFINED,
but it contains the original method entry. If the original method
entry is present, we shouldn't skip the method when searching even
when skipping refined methods.

Fixes [Bug #17519]

Updated by nagachika (Tomoyuki Chikanaga) over 4 years ago Actions #14 [ruby-core:103583]

  • Backport changed from 2.5: UNKNOWN, 2.6: REQUIRED, 2.7: DONE, 3.0: DONE to 2.5: UNKNOWN, 2.6: REQUIRED, 2.7: REQUIRED, 3.0: REQUIRED

reset Backport field to backport git|4b36a597f48c857aa5eb9ed80fec0d02f6284646.

Updated by nagachika (Tomoyuki Chikanaga) over 4 years ago Actions #15 [ruby-core:104095]

  • Backport changed from 2.5: UNKNOWN, 2.6: REQUIRED, 2.7: REQUIRED, 3.0: REQUIRED to 2.5: UNKNOWN, 2.6: REQUIRED, 2.7: REQUIRED, 3.0: DONE

ruby_3_0 7b6a2ad04a3272a31323493133498dfc60d77d76 merged revision(s) 4b36a597f48c857aa5eb9ed80fec0d02f6284646.

Actions

Also available in: PDF Atom