From 85a337f986fe6da99c7f8358f790f17b122b3903 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Sat, 13 Jul 2019 12:04:01 -0400 Subject: Kernel#lambda: return forwarded block as non-lambda proc Before this commit, Kernel#lambda can't tell the difference between a directly passed literal block and one passed with an ampersand. A block passed with an ampersand is semantically speaking already a non-lambda proc. When Kernel#lambda receives a non-lambda proc, it should simply return it. Implementation wise, when the VM calls a method with a literal block, it places the code for the block on the calling control frame and passes a pointer (block handler) to the callee. Before this commit, the VM forwards block arguments by simply forwarding the block handler, which leaves the slot for block code unused when a control frame forwards its block argument. I use the vacant space to indicate that a frame has forwarded its block argument and inspect that in Kernel#lambda to detect forwarded blocks. This is a very ad-hoc solution and relies *heavily* on the way block passing works in the VM. However, it's the most self-contained solution I have. [Bug #15620] --- proc.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'proc.c') diff --git a/proc.c b/proc.c index 54b044f421..b4899d2f1f 100644 --- a/proc.c +++ b/proc.c @@ -792,8 +792,16 @@ proc_new(VALUE klass, int8_t is_lambda, int8_t kernel) break; case block_handler_type_ifunc: - case block_handler_type_iseq: return rb_vm_make_proc_lambda(ec, VM_BH_TO_CAPT_BLOCK(block_handler), klass, is_lambda); + case block_handler_type_iseq: + { + const struct rb_captured_block *captured = VM_BH_TO_CAPT_BLOCK(block_handler); + rb_control_frame_t *last_ruby_cfp = rb_vm_get_ruby_level_next_cfp(ec, cfp); + if (is_lambda && last_ruby_cfp && vm_cfp_forwarded_bh_p(last_ruby_cfp, block_handler)) { + is_lambda = false; + } + return rb_vm_make_proc_lambda(ec, captured, klass, is_lambda); + } } VM_UNREACHABLE(proc_new); return Qnil; -- cgit v1.2.3