From ae163cae6b3f01e0fb827e0a18d5889f9703617f Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 27 May 2022 13:47:06 -0700 Subject: [PATCH] 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] --- test/ruby/test_refinement.rb | 35 +++++++++++++++++++++++++++++++++++ vm_insnhelper.c | 22 ++++++++++++++++------ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb index c0754d8cf0..56f33ae00a 100644 --- a/test/ruby/test_refinement.rb +++ b/test/ruby/test_refinement.rb @@ -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]' diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 09fcd2f729..d2f3fc62bd 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -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) &&