Make Object#singleton_method return methods in modules included in or prepended to singleton class

To simplify the implementation, this makes Object#singleton_method
call the same method called by Object#method (rb_obj_method), then
check that the returned Method is defined before the superclass of the
object's singleton class.  To keep the same error messages, it rescues
exceptions raised by rb_obj_method, and then raises its own exception.

Fixes [Bug #20620]
This commit is contained in:
Jeremy Evans 2024-09-11 21:29:41 -07:00
parent dc83de4928
commit 9986a7c393
Notes: git 2024-10-03 14:27:18 +00:00
2 changed files with 64 additions and 13 deletions

45
proc.c
View File

@ -2046,6 +2046,19 @@ rb_obj_public_method(VALUE obj, VALUE vid)
return obj_method(obj, vid, TRUE);
}
static VALUE
rb_obj_singleton_method_lookup(VALUE arg)
{
VALUE *args = (VALUE *)arg;
return rb_obj_method(args[0], args[1]);
}
static VALUE
rb_obj_singleton_method_lookup_fail(VALUE arg1, VALUE arg2)
{
return Qfalse;
}
/*
* call-seq:
* obj.singleton_method(sym) -> method
@ -2073,11 +2086,12 @@ rb_obj_public_method(VALUE obj, VALUE vid)
VALUE
rb_obj_singleton_method(VALUE obj, VALUE vid)
{
VALUE klass = rb_singleton_class_get(obj);
VALUE sc = rb_singleton_class_get(obj);
VALUE klass;
ID id = rb_check_id(&vid);
if (NIL_P(klass) ||
NIL_P(klass = RCLASS_ORIGIN(klass)) ||
if (NIL_P(sc) ||
NIL_P(klass = RCLASS_ORIGIN(sc)) ||
!NIL_P(rb_special_singleton_class(obj))) {
/* goto undef; */
}
@ -2087,21 +2101,26 @@ rb_obj_singleton_method(VALUE obj, VALUE vid)
/* else goto undef; */
}
else {
const rb_method_entry_t *me = rb_method_entry_at(klass, id);
vid = ID2SYM(id);
VALUE args[2] = {obj, vid};
VALUE ruby_method = rb_rescue(rb_obj_singleton_method_lookup, (VALUE)args, rb_obj_singleton_method_lookup_fail, Qfalse);
if (ruby_method) {
struct METHOD *method = (struct METHOD *)RTYPEDDATA_GET_DATA(ruby_method);
VALUE lookup_class = RBASIC_CLASS(obj);
VALUE stop_class = rb_class_superclass(sc);
VALUE method_class = method->iclass;
if (UNDEFINED_METHOD_ENTRY_P(me)) {
/* goto undef; */
}
else if (UNDEFINED_REFINED_METHOD_P(me->def)) {
/* goto undef; */
}
else {
return mnew_from_me(me, klass, klass, obj, id, rb_cMethod, FALSE);
/* Determine if method is in singleton class, or module included in or prepended to it */
do {
if (lookup_class == method_class) {
return ruby_method;
}
lookup_class = RCLASS_SUPER(lookup_class);
} while (lookup_class && lookup_class != stop_class);
}
}
/* undef: */
vid = ID2SYM(id);
rb_name_err_raise("undefined singleton method '%1$s' for '%2$s'",
obj, vid);
UNREACHABLE_RETURN(Qundef);

View File

@ -940,6 +940,38 @@ class TestMethod < Test::Unit::TestCase
assert_raise(NameError, bug14658) {o.singleton_method(:bar)}
end
def test_singleton_method_included_or_prepended_bug_20620
m = Module.new do
extend self
def foo = :foo
end
assert_equal(:foo, m.singleton_method(:foo).call)
assert_raise(NameError) {m.singleton_method(:puts)}
sc = Class.new do
def t = :t
end
c = Class.new(sc) do
singleton_class.prepend(Module.new do
def bar = :bar
end)
extend(Module.new do
def quux = :quux
end)
def self.baz = :baz
end
assert_equal(:bar, c.singleton_method(:bar).call)
assert_equal(:baz, c.singleton_method(:baz).call)
assert_equal(:quux, c.singleton_method(:quux).call)
assert_raise(NameError) {c.singleton_method(:t)}
c2 = Class.new(c)
assert_raise(NameError) {c2.singleton_method(:bar)}
assert_raise(NameError) {c2.singleton_method(:baz)}
assert_raise(NameError) {c2.singleton_method(:quux)}
end
Feature9783 = '[ruby-core:62212] [Feature #9783]'
def assert_curry_three_args(m)