Allow calling protected methods from refinements

Previously protected methods on refinements could never be called
because they were seen as being "defined" on the hidden refinement
ICLASS.

This commit updates calling refined protected methods so that they are
considered to be defined on the original class (the one being refined).

This ended up using the same behaviour that was used to check whether a
call to super was allowed, so I extracted that into a method.

[Bug #18806]
This commit is contained in:
John Hawthorn 2022-05-27 13:47:06 -07:00
parent cd5cafa4a3
commit ae163cae6b
Notes: git 2022-06-17 05:25:11 +09:00
2 changed files with 51 additions and 6 deletions

View File

@ -1206,6 +1206,41 @@ class TestRefinement < Test::Unit::TestCase
INPUT
end
def test_refined_protected_methods
assert_separately([], <<-"end;")
bug18806 = '[ruby-core:108705] [Bug #18806]'
class C; end
module R
refine C do
def refined_call_foo = foo
def refined_call_foo_on(other) = other.foo
protected
def foo = :foo
end
end
class C
using R
def call_foo = foo
def call_foo_on(other) = other.foo
end
c = C.new
assert_equal :foo, c.call_foo, bug18806
assert_equal :foo, c.call_foo_on(c), bug18806
assert_equal :foo, c.call_foo_on(C.new), bug18806
using R
assert_equal :foo, c.refined_call_foo, bug18806
assert_equal :foo, c.refined_call_foo_on(c), bug18806
assert_equal :foo, c.refined_call_foo_on(C.new), bug18806
end;
end
def test_refine_basic_object
assert_separately([], <<-"end;")
bug10106 = '[ruby-core:64166] [Bug #10106]'

View File

@ -3711,6 +3711,19 @@ vm_call_method_nome(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct
}
}
/* Protected method calls and super invocations need to check that the receiver
* (self for super) inherits the module on which the method is defined.
* In the case of refinements, it should consider the original class not the
* refinement.
*/
static VALUE
vm_defined_class_for_protected_call(const rb_callable_method_entry_t *me)
{
VALUE defined_class = me->defined_class;
VALUE refined_class = RCLASS_REFINED_CLASS(defined_class);
return NIL_P(refined_class) ? defined_class : refined_class;
}
static inline VALUE
vm_call_method(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling)
{
@ -3737,7 +3750,8 @@ vm_call_method(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_ca
case METHOD_VISI_PROTECTED:
if (!(vm_ci_flag(ci) & VM_CALL_OPT_SEND)) {
if (!rb_obj_is_kind_of(cfp->self, vm_cc_cme(cc)->defined_class)) {
VALUE defined_class = vm_defined_class_for_protected_call(vm_cc_cme(cc));
if (!rb_obj_is_kind_of(cfp->self, defined_class)) {
vm_cc_method_missing_reason_set(cc, MISSING_PROTECTED);
return vm_call_method_missing(ec, cfp, calling);
}
@ -3834,11 +3848,7 @@ vm_search_super_method(const rb_control_frame_t *reg_cfp, struct rb_call_data *c
vm_super_outside();
}
current_defined_class = me->defined_class;
if (!NIL_P(RCLASS_REFINED_CLASS(current_defined_class))) {
current_defined_class = RCLASS_REFINED_CLASS(current_defined_class);
}
current_defined_class = vm_defined_class_for_protected_call(me);
if (BUILTIN_TYPE(current_defined_class) != T_MODULE &&
reg_cfp->iseq != method_entry_iseqptr(me) &&