Bug #12832
closedCalling Object#method hangs for private method defined on module then made public once it's been used to extend class
Description
module Foo
private
def foo
"foo"
end
end
class Bar
extend Foo
class << self
public :foo
end
end
Bar.foo # => "foo"
Bar.method(:foo) # => #<Method: Class(Bar)#foo>
module Baz
end
class Bar
class << self
prepend Baz
end
end
Bar.method(:foo) # => hangs!
Runs OK¶
- ruby 2.0.0p648 (2015-12-16 revision 53162) [x86_64-darwin14.5.0]
- ruby 2.1.0p0 (2013-12-25 revision 44422) [x86_64-darwin14.0]
- ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-darwin14.0]
- ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin14.0]
Hangs on last line¶
- ruby 2.1.3p242 (2014-09-19 revision 47630) [x86_64-darwin14.0]
- ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-darwin14.0]
- ruby 2.1.10p492 (2016-04-01 revision 54464) [x86_64-darwin14.0]
- ruby 2.2.5p319 (2016-04-26 revision 54774) [x86_64-darwin14]
- ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin14]
- ruby 2.4.0preview2 (2016-09-09 trunk 56129) [x86_64-darwin14]
Updated by shyouhei (Shyouhei Urabe) about 8 years ago
- Status changed from Open to Assigned
- Assignee set to ko1 (Koichi Sasada)
I can reproduce this. Seems like a infinite loop inside ofrb_callable_method_entry_without_refinements
. I think this is a showstopper bug.
zsh % lldb ./miniruby target.rb
(lldb) target create "./miniruby"
Current executable set to './miniruby' (x86_64).
(lldb) settings set -- target.run-args "target.rb"
(lldb) run
Process 23728 launched: './miniruby' (x86_64)
Process 23728 stopped
* thread #1: tid = 0xea3fd, 0x00000001001f2535 miniruby`rb_callable_method_entry_without_refinements + 24 at vm_method.c:848, stop reason = signal SIGSTOP
frame #0: 0x00000001001f2535 miniruby`rb_callable_method_entry_without_refinements + 24 at vm_method.c:848
845 const rb_method_entry_t *me = method_entry_get(klass, id, defined_class_ptr);
846
847 if (me) {
-> 848 if (me->def->type == VM_METHOD_TYPE_REFINED) {
849 if (with_refinement) {
850 const rb_cref_t *cref = rb_vm_cref();
851 VALUE refinements = cref ? CREF_REFINEMENTS(cref) : Qnil;
(lldb) bt
* thread #1: tid = 0xea3fd, 0x00000001001f2535 miniruby`rb_callable_method_entry_without_refinements + 24 at vm_method.c:848, stop reason = signal SIGSTOP
* frame #0: 0x00000001001f2535 miniruby`rb_callable_method_entry_without_refinements + 24 at vm_method.c:848
frame #1: 0x00000001001f251d miniruby`rb_callable_method_entry_without_refinements(klass=<unavailable>, id=26065) + 13
frame #2: 0x000000010011d820 miniruby`mnew_internal(me=0x000000010185ec40, klass=4320521560, obj=4320521600, id=26065, mclass=4320688000, scope=0, error=1) + 96 at proc.c:1375
frame #3: 0x00000001001210f3 miniruby`rb_obj_method [inlined] mnew_from_me(scope=<unavailable>, mclass=<unavailable>, id=<unavailable>, obj=<unavailable>, klass=<unavailable>, me=<unavailable>) + 26 at proc.c:1403
frame #4: 0x00000001001210d9 miniruby`rb_obj_method + 24
frame #5: 0x00000001001210c1 miniruby`rb_obj_method + 84
frame #6: 0x000000010012106d miniruby`rb_obj_method(obj=4320521600, vid=<unavailable>) + 13
frame #7: 0x00000001001f0e56 miniruby`vm_call_cfunc + 184 at vm_insnhelper.c:1752
frame #8: 0x00000001001f0d9e miniruby`vm_call_cfunc(th=0x0000000100604310, reg_cfp=0x00000001007fffa0, calling=<unavailable>, ci=<unavailable>, cc=<unavailable>) + 46
frame #9: 0x00000001001ff3fe miniruby`vm_call_method_each_type(th=0x0000000100604310, cfp=0x00000001007fffa0, calling=0x00007fff5fbfd900, ci=<unavailable>, cc=<unavailable>) + 142 at vm_insnhelper.c:2138
frame #10: 0x00000001001ff95b miniruby`vm_call_method(th=0x0000000100604310, cfp=0x00000001007fffa0, calling=<unavailable>, ci=<unavailable>, cc=<unavailable>) + 235 at vm_insnhelper.c:2288
frame #11: 0x00000001001f8639 miniruby`vm_exec_core(th=0x0000000100604310, initial=<unavailable>) + 6217 at insns.def:1066
frame #12: 0x00000001001fdda3 miniruby`vm_exec(th=0x0000000100604310) + 131 at vm.c:1711
frame #13: 0x0000000100082bc0 miniruby`ruby_exec_internal(n=0x000000010185f9d8) + 176 at eval.c:244
frame #14: 0x000000010008686f miniruby`ruby_run_node [inlined] ruby_exec_node(n=<unavailable>) + 47 at eval.c:308
frame #15: 0x000000010008685b miniruby`ruby_run_node(n=<unavailable>) + 27
frame #16: 0x000000010021788e miniruby`main(argc=<unavailable>, argv=<unavailable>) + 78 at main.c:36
frame #17: 0x00007fff8dc4f5ad libdyld.dylib`start + 1
frame #18: 0x00007fff8dc4f5ad libdyld.dylib`start + 1
(lldb)
Updated by nagachika (Tomoyuki Chikanaga) about 8 years ago
- Backport changed from 2.1: UNKNOWN, 2.2: UNKNOWN, 2.3: UNKNOWN to 2.1: REQUIRED, 2.2: REQUIRED, 2.3: REQUIRED
Updated by floehopper (James Mead) about 8 years ago
In case it helps, it seems as if Ruby v2.3 is "more" broken than earlier versions. If I remove the Bar.method(:foo)
lookup before the Baz
module is prepended, the final lookup still hangs in Ruby v2.3, but not in v2.2. See below:
module Foo
private
def foo
"foo"
end
end
class Bar
extend Foo
class << self
public :foo
end
end
module Baz
end
class Bar
class << self
prepend Baz
end
end
Bar.method(:foo) # => hangs in Ruby v2.3.1, but not v2.2.5
Updated by chrisroos (Chris Roos) about 8 years ago
I'm not sure whether this is useful information but calling #instance_method
on Bar
's metaclass also causes Ruby to hang. Using James's most recent code snippet, the following code seems to cause the same problem as Bar.method(:foo)
.
metaclass = (class << Bar; self; end)
p metaclass.instance_method(:foo) # => hangs in Ruby v2.3.1, but not v2.2.5
Updated by nobu (Nobuyoshi Nakada) about 8 years ago
- Status changed from Assigned to Closed
Applied in changeset r56489.
proc.c: follow the original class
- proc.c (mnew_internal): follow the original class, not to loop
the prepended module. [ruby-core:77591] [Bug #12832]
Updated by floehopper (James Mead) about 8 years ago
@nobu (Nobuyoshi Nakada): Thank you for fixing this. Is it expected that a bug like this would cause the interpreter not to accept interrupt signals?
Updated by nobu (Nobuyoshi Nakada) about 8 years ago
It's not expected but a bug is always what causes unexpected behavior.
Updated by floehopper (James Mead) about 8 years ago
@nobu (Nobuyoshi Nakada): Is it possible there is a separate problem with the code such that interrupt signals are incorrectly being ignored...?
Updated by kernigh (George Koehler) about 8 years ago
Ruby defers signals. At safe moments, Ruby checks if a signal arrived, then handles it. If Ruby is stuck in an infinite loop (because of a bug like this one), then Ruby would never check if a signal arrived, so the signal would be ignored. After fixing the bug, signals should work again. Ruby's deferred signals seem like Perl's, see http://perldoc.perl.org/perlipc.html#Deferred-Signals-(Safe-Signals)
Updated by floehopper (James Mead) about 8 years ago
@George: Thanks for explaining. Makes sense.
Updated by nagachika (Tomoyuki Chikanaga) about 8 years ago
- Backport changed from 2.1: REQUIRED, 2.2: REQUIRED, 2.3: REQUIRED to 2.1: REQUIRED, 2.2: REQUIRED, 2.3: DONE
ruby_2_3 r56720 merged revision(s) 56489.
Updated by usa (Usaku NAKAMURA) about 8 years ago
- Backport changed from 2.1: REQUIRED, 2.2: REQUIRED, 2.3: DONE to 2.1: REQUIRED, 2.2: DONE, 2.3: DONE
ruby_2_2 r56731 merged revision(s) 56489.