Project

General

Profile

Actions

Bug #21873

closed

`UnboundMethod#==` returns false for methods from included/extended modules

Bug #21873: `UnboundMethod#==` returns false for methods from included/extended modules

Added by mdalessio (Mike Dalessio) 3 days ago. Updated 1 day ago.

Status:
Closed
Assignee:
-
Target version:
-
[ruby-core:124741]

Description

Description

UnboundMethod#== returns false when comparing a module's instance method against the same method obtained via Method#unbind on a class that includes or extends that module, despite having the same owner and source location.

module MyMethods
  def hello = "hello"
end

class Base
  extend MyMethods
end

from_module = MyMethods.instance_method(:hello)
from_unbind = Base.method(:hello).unbind

p from_module.owner == from_unbind.owner                     #=> true
p from_module.source_location == from_unbind.source_location #=> true
p from_module.inspect == from_unbind.inspect                 #=> true

p from_module == from_unbind                                 #=> false (expected true)

Diagnosis

method_eq compares method entries using method_entry_defined_class. For methods mixed in via include/extend, the "defined class" will be an ICLASS and not the original class/module. In the example above, method_eq is comparing a module's ICLASS entry against the module, and that will always be false.

Related: #18798 (fixed by @ko1 (Koichi Sasada) in 59e389af).

Updated by mdalessio (Mike Dalessio) 3 days ago ยท Edited Actions #1 [ruby-core:124742]

A potential fix for this might be:

diff --git a/proc.c b/proc.c
index f0cdcae7..67963a1b 100644
--- a/proc.c
+++ b/proc.c
@@ -2006,6 +2006,8 @@ method_eq(VALUE method, VALUE other)

     klass1 = method_entry_defined_class(m1->me);
     klass2 = method_entry_defined_class(m2->me);
+    if (RB_TYPE_P(klass1, T_ICLASS)) klass1 = RBASIC_CLASS(klass1);
+    if (RB_TYPE_P(klass2, T_ICLASS)) klass2 = RBASIC_CLASS(klass2);

     if (!rb_method_entry_eq(m1->me, m2->me) ||
         klass1 != klass2 ||

See https://github.com/ruby/ruby/pull/16120

Updated by mdalessio (Mike Dalessio) 3 days ago Actions #2

  • Subject changed from `UnboundMethod#==` returns false for methods obtained via extend + unbind to `UnboundMethod#==` returns false for methods from included/extended modules

Updated by Anonymous 3 days ago Actions #3

  • Status changed from Open to Closed

Applied in changeset git|836c1700104c305fa13cbc7b17d114705dd807b2.


Fix UnboundMethod#== for methods from included/extended modules

Method#unbind clones the method entry, preserving its defined_class.
For methods mixed in via include/extend, defined_class is an ICLASS,
causing UnboundMethod#== to return false when comparing against the
same method obtained via Module#instance_method.

Resolve ICLASS defined_class in method_eq.

Fixes [Bug #21873]

Updated by Eregon (Benoit Daloze) 2 days ago Actions #4 [ruby-core:124763]

  • Backport changed from 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN to 3.2: REQUIRED, 3.3: REQUIRED, 3.4: REQUIRED, 4.0: REQUIRED

Marking as REQUIRED to backport to all versions since all versions seem affected.

Updated by k0kubun (Takashi Kokubun) 1 day ago Actions #5 [ruby-core:124765]

  • Backport changed from 3.2: REQUIRED, 3.3: REQUIRED, 3.4: REQUIRED, 4.0: REQUIRED to 3.2: REQUIRED, 3.3: REQUIRED, 3.4: REQUIRED, 4.0: DONE

Updated by byroot (Jean Boussier) 1 day ago 1Actions #6 [ruby-core:124768]

  • Backport changed from 3.2: REQUIRED, 3.3: REQUIRED, 3.4: REQUIRED, 4.0: DONE to 3.2: WONTFIX, 3.3: REQUIRED, 3.4: REQUIRED, 4.0: DONE

3.2 is in security only maintenance though.

Actions

Also available in: PDF Atom