merge revision(s) c60aaed185: [Backport #17130]

Fix Method#super_method for aliased methods

	Previously, Method#super_method looked at the called_id to
	determine the method id to use, but that isn't correct for
	aliased methods, because the super target depends on the
	original method id, not the called_id.

	Additionally, aliases can reference methods defined in other
	classes and modules, and super lookup needs to start in the
	super of the defined class in such cases.

	This adds tests for Method#super_method for both types of
	aliases, one that uses VM_METHOD_TYPE_ALIAS and another that
	does not.  Both check that the results for calling super
	methods return the expected values.

	To find the defined class for alias methods, add an rb_ prefix
	to find_defined_class_by_owner in vm_insnhelper.c and make it
	non-static, so that it can be called from method_super_method
	in proc.c.

	This bug was original discovered while researching [Bug #11189].

	Fixes [Bug #17130]
	---
	 proc.c                   | 13 ++++++--
	 test/ruby/test_method.rb | 80 ++++++++++++++++++++++++++++++++++++++++++++++++
	 vm_insnhelper.c          |  6 ++--
	 3 files changed, 94 insertions(+), 5 deletions(-)
This commit is contained in:
nagachika 2021-03-20 13:29:39 +09:00
parent 70c3a195f3
commit c98aa2db60
4 changed files with 95 additions and 6 deletions

13
proc.c
View file

@ -3024,6 +3024,8 @@ method_to_proc(VALUE method)
return procval; return procval;
} }
extern VALUE rb_find_defined_class_by_owner(VALUE current_class, VALUE target_owner);
/* /*
* call-seq: * call-seq:
* meth.super_method -> method * meth.super_method -> method
@ -3043,8 +3045,15 @@ method_super_method(VALUE method)
TypedData_Get_Struct(method, struct METHOD, &method_data_type, data); TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);
iclass = data->iclass; iclass = data->iclass;
if (!iclass) return Qnil; if (!iclass) return Qnil;
super_class = RCLASS_SUPER(RCLASS_ORIGIN(iclass)); if (data->me->def->type == VM_METHOD_TYPE_ALIAS) {
mid = data->me->called_id; super_class = RCLASS_SUPER(rb_find_defined_class_by_owner(data->me->defined_class,
data->me->def->body.alias.original_me->owner));
mid = data->me->def->body.alias.original_me->def->original_id;
}
else {
super_class = RCLASS_SUPER(RCLASS_ORIGIN(iclass));
mid = data->me->def->original_id;
}
if (!super_class) return Qnil; if (!super_class) return Qnil;
me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(super_class, mid, &iclass); me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(super_class, mid, &iclass);
if (!me) return Qnil; if (!me) return Qnil;

View file

@ -1064,6 +1064,86 @@ class TestMethod < Test::Unit::TestCase
'[ruby-core:85231] [Bug #14421]' '[ruby-core:85231] [Bug #14421]'
end end
def test_super_method_alias
c0 = Class.new do
def m1
[:C0_m1]
end
def m2
[:C0_m2]
end
end
c1 = Class.new(c0) do
def m1
[:C1_m1] + super
end
alias m2 m1
end
c2 = Class.new(c1) do
def m2
[:C2_m2] + super
end
end
o1 = c2.new
assert_equal([:C2_m2, :C1_m1, :C0_m1], o1.m2)
m = o1.method(:m2)
assert_equal([:C2_m2, :C1_m1, :C0_m1], m.call)
m = m.super_method
assert_equal([:C1_m1, :C0_m1], m.call)
m = m.super_method
assert_equal([:C0_m1], m.call)
assert_nil(m.super_method)
end
def test_super_method_alias_to_prepended_module
m = Module.new do
def m1
[:P_m1] + super
end
def m2
[:P_m2] + super
end
end
c0 = Class.new do
def m1
[:C0_m1]
end
end
c1 = Class.new(c0) do
def m1
[:C1_m1] + super
end
prepend m
alias m2 m1
end
o1 = c1.new
assert_equal([:P_m2, :P_m1, :C1_m1, :C0_m1], o1.m2)
m = o1.method(:m2)
assert_equal([:P_m2, :P_m1, :C1_m1, :C0_m1], m.call)
m = m.super_method
assert_equal([:P_m1, :C1_m1, :C0_m1], m.call)
m = m.super_method
assert_equal([:C1_m1, :C0_m1], m.call)
m = m.super_method
assert_equal([:C0_m1], m.call)
assert_nil(m.super_method)
end
def rest_parameter(*rest) def rest_parameter(*rest)
rest rest
end end

View file

@ -2,7 +2,7 @@
# define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR
#define RUBY_VERSION_TEENY 3 #define RUBY_VERSION_TEENY 3
#define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR
#define RUBY_PATCHLEVEL 164 #define RUBY_PATCHLEVEL 165
#define RUBY_RELEASE_YEAR 2021 #define RUBY_RELEASE_YEAR 2021
#define RUBY_RELEASE_MONTH 3 #define RUBY_RELEASE_MONTH 3

View file

@ -2794,8 +2794,8 @@ current_method_entry(const rb_execution_context_t *ec, rb_control_frame_t *cfp)
return cfp; return cfp;
} }
static VALUE MJIT_FUNC_EXPORTED VALUE
find_defined_class_by_owner(VALUE current_class, VALUE target_owner) rb_find_defined_class_by_owner(VALUE current_class, VALUE target_owner)
{ {
VALUE klass = current_class; VALUE klass = current_class;
@ -2820,7 +2820,7 @@ aliased_callable_method_entry(const rb_callable_method_entry_t *me)
const rb_callable_method_entry_t *cme; const rb_callable_method_entry_t *cme;
if (orig_me->defined_class == 0) { if (orig_me->defined_class == 0) {
VALUE defined_class = find_defined_class_by_owner(me->defined_class, orig_me->owner); VALUE defined_class = rb_find_defined_class_by_owner(me->defined_class, orig_me->owner);
VM_ASSERT(RB_TYPE_P(orig_me->owner, T_MODULE)); VM_ASSERT(RB_TYPE_P(orig_me->owner, T_MODULE));
cme = rb_method_entry_complement_defined_class(orig_me, me->called_id, defined_class); cme = rb_method_entry_complement_defined_class(orig_me, me->called_id, defined_class);