[前][次][番号順一覧][スレッド一覧]

ruby-changes:62730

From: Jeremy <ko1@a...>
Date: Fri, 28 Aug 2020 00:37:29 +0900 (JST)
Subject: [ruby-changes:62730] c60aaed185 (master): Fix Method#super_method for aliased methods

https://git.ruby-lang.org/ruby.git/commit/?id=c60aaed185

From c60aaed1856b2b6f90de0992c34771830019e021 Mon Sep 17 00:00:00 2001
From: Jeremy Evans <code@j...>
Date: Thu, 27 Aug 2020 08:37:03 -0700
Subject: 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]

diff --git a/proc.c b/proc.c
index eb3e4bb..3f92ceb 100644
--- a/proc.c
+++ b/proc.c
@@ -3116,6 +3116,8 @@ method_to_proc(VALUE method) https://github.com/ruby/ruby/blob/trunk/proc.c#L3116
     return procval;
 }
 
+extern VALUE rb_find_defined_class_by_owner(VALUE current_class, VALUE target_owner);
+
 /*
  * call-seq:
  *   meth.super_method  -> method
@@ -3135,8 +3137,15 @@ method_super_method(VALUE method) https://github.com/ruby/ruby/blob/trunk/proc.c#L3137
     TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);
     iclass = data->iclass;
     if (!iclass) return Qnil;
-    super_class = RCLASS_SUPER(RCLASS_ORIGIN(iclass));
-    mid = data->me->called_id;
+    if (data->me->def->type == VM_METHOD_TYPE_ALIAS) {
+        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;
     me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(super_class, mid, &iclass);
     if (!me) return Qnil;
diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb
index 43c6c6d..85c5c45 100644
--- a/test/ruby/test_method.rb
+++ b/test/ruby/test_method.rb
@@ -1080,6 +1080,86 @@ class TestMethod < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_method.rb#L1080
       '[ruby-core:85231] [Bug #14421]'
   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)
     rest
   end
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index e6228fb..f1c293a 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -2673,8 +2673,8 @@ vm_call_bmethod(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_c https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L2673
     return vm_call_bmethod_body(ec, calling, cd, argv);
 }
 
-static VALUE
-find_defined_class_by_owner(VALUE current_class, VALUE target_owner)
+MJIT_FUNC_EXPORTED VALUE
+rb_find_defined_class_by_owner(VALUE current_class, VALUE target_owner)
 {
     VALUE klass = current_class;
 
@@ -2702,7 +2702,7 @@ aliased_callable_method_entry(const rb_callable_method_entry_t *me) https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L2702
     const rb_callable_method_entry_t *cme;
 
     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));
 	cme = rb_method_entry_complement_defined_class(orig_me, me->called_id, defined_class);
 
-- 
cgit v0.10.2


--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]