Bug #22504
closedEmpty keyword hash mishandled when calling a Method proc through the C API
Description
Calling a Proc created by Method#to_proc through rb_proc_call_kw or rb_proc_call_with_block_kw with RB_PASS_KEYWORDS and an empty keyword hash can raise TypeError, even though the equivalent Ruby call succeeds.
Reproduction¶
// proc_kw.c
#include "ruby.h"
static VALUE call_kw(VALUE self, VALUE proc, VALUE args)
{
return rb_proc_call_kw(proc, args, RB_PASS_KEYWORDS);
}
static VALUE call_with_block_kw(VALUE self, VALUE proc, VALUE args)
{
return rb_proc_call_with_block_kw(proc, RARRAY_LENINT(args), RARRAY_PTR(args),
Qnil, RB_PASS_KEYWORDS);
}
void Init_proc_kw(void)
{
VALUE mod = rb_define_module("ProcKW");
rb_define_singleton_method(mod, "call_kw", call_kw, 2);
rb_define_singleton_method(mod, "call_with_block_kw", call_with_block_kw, 2);
}
# repro.rb
require_relative "proc_kw"
def target(*args, **kwargs) = [args, kwargs]
pr = method(:target).to_proc
p pr.call(1, **{}) # => [[1], {}]
p ProcKW.call_kw(pr, [1, {}]) # => TypeError. This should be [[1], {}].
p ProcKW.call_with_block_kw(pr, [1, {}]) # => TypeError. This should be [[1], {}].
Cause and proposed fix¶
In the block_type_ifunc branch of invoke_block_from_c_proc, the empty keyword hash is removed by decrementing argc, but kw_splat remains set. The forwarded call consequently treats the last positional argument as a keyword hash.
PR #19607 clears kw_splat when removing the empty keyword hash, matching CALLER_SETUP_ARG.
This was originally reported through ZJIT in Shopify/ruby#1092 , but the underlying VM bug also affects these C APIs independently of ZJIT.
I also confirmed this on Ruby 3.3.13 and 3.4.12
Backport consideration¶
Should this fix also be backported?