From: "jeremyevans0 (Jeremy Evans)" Date: 2022-06-14T00:12:19+00:00 Subject: [ruby-core:108883] [Ruby master Bug#18826] Symbol#to_proc inconsistent, sometimes calls private methods Issue #18826 has been updated by jeremyevans0 (Jeremy Evans). This appears to be caused by the use of the `VM_FCALL` flag in `vm_call_symbol`. Since `:foo.to_proc` should be treated as `lambda{|t| t.foo}`, not `lambda{|t| t.send(:foo)}`, I agree that this is a bug. Fixing this for private methods is not too difficult, by adding a `flags` argument to `vm_call_symbol`, and only using `VM_CALL_FCALL` for the `vm_call_opt_send` case and not the `vm_invoke_symbol_block` case. For protected methods, it's more challenging, since calling `tap` pushes a new VM frame where `cfp->self` is the receiver of `tap`, and the method called by `Symbol#to_proc` will always be called on that receiver, so protected methods would not raise exceptions. Hopefully @nobu can figure out how best to handle the protected method case. With your second example, if you switch `private` to `protected`, you get a `NoMethodError`, which makes sense, since you have an a range receiver (`1..4`). calling an integer method (`foo`). For cases where the receiver matches, both `tap` and `collect` appear to allow protected methods: ```ruby class Array protected def foo first end end a = [[1], [2], [3]] p a.tap(&:foo) p a.collect(&:foo) ``` So it appears to that `:foo.to_proc` should be handled the same as `lambda{|t| t.foo}` in regards to protected methods, unless we want to break backwards compatibility. Here's a fix for the private method case: ```diff diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 09fcd2f729..33daada3cf 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -3201,7 +3201,7 @@ ci_missing_reason(const struct rb_callinfo *ci) static VALUE vm_call_symbol(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, - struct rb_calling_info *calling, const struct rb_callinfo *ci, VALUE symbol) + struct rb_calling_info *calling, const struct rb_callinfo *ci, VALUE symbol, int flags) { ASSUME(calling->argc >= 0); /* Also assumes CALLER_SETUP_ARG is already done. */ @@ -3211,9 +3211,7 @@ vm_call_symbol(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, VALUE recv = calling->recv; VALUE klass = CLASS_OF(recv); ID mid = rb_check_id(&symbol); - int flags = VM_CALL_FCALL | - VM_CALL_OPT_SEND | - (calling->kw_splat ? VM_CALL_KW_SPLAT : 0); + flags |= VM_CALL_OPT_SEND | (calling->kw_splat ? VM_CALL_KW_SPLAT : 0); if (UNLIKELY(! mid)) { mid = idMethodMissing; @@ -3300,7 +3298,7 @@ vm_call_opt_send(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct calling->argc -= 1; DEC_SP(1); - return vm_call_symbol(ec, reg_cfp, calling, calling->ci, sym); + return vm_call_symbol(ec, reg_cfp, calling, calling->ci, sym, VM_CALL_FCALL); } } @@ -4104,7 +4102,7 @@ vm_invoke_symbol_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, VALUE symbol = VM_BH_TO_SYMBOL(block_handler); CALLER_SETUP_ARG(reg_cfp, calling, ci); calling->recv = TOPN(--calling->argc); - return vm_call_symbol(ec, reg_cfp, calling, ci, symbol); + return vm_call_symbol(ec, reg_cfp, calling, ci, symbol, 0); } } ``` ---------------------------------------- Bug #18826: Symbol#to_proc inconsistent, sometimes calls private methods https://bugs.ruby-lang.org/issues/18826#change-97962 * Author: bjfish (Brandon Fish) * Status: Open * Priority: Normal * ruby -v: 3.0.3 * Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN ---------------------------------------- The following usage calls a protected method and prints "hello": ``` ruby class Test protected def referenced_columns puts "hello" end end Test.new.tap(&:referenced_columns) ``` However, the following usage results in a NoMethodError: ``` ruby class Integer private def foo 42 end end (1..4).collect(&:foo) ``` It seems to be a bug that tap calls a private method. It is also inconsistent with collect not calling private methods. -- https://bugs.ruby-lang.org/ Unsubscribe: