Bug #22426
openZJIT: keyword arguments swapped when passed out of declared order to a method with a block (please backport d81a11d4e6 to 4.0)
Description
Hello!
On Ruby 4.0.7 with ZJIT enabled, keyword arguments can reach the wrong parameters after the method is compiled. It happens when the caller passes keywords in a different order than the callee declares them, and the callee body contains a block. The values are swapped silently, so the result is wrong data, not a crash.
We hit this in a staging environment through the jwt gem (3.3.0). JWT::Encode#segments calls @token.sign!(algorithm: @algorithm, key: @key), while JWT::Token#sign! is declared as def sign!(key:, algorithm:) and calls .tap do ... end in its body. After about 30 calls, JWT::JWA.resolve receives the OpenSSL::PKey::EC key instead of the 'ES256' string and raises ArgumentError: Custom algorithms are required to include JWT::JWA::SigningAlgorithm.
Reproduction (no gems):
# frozen_string_literal: true
module JWA
Signer = Struct.new(:algorithm, :key)
def self.create_signer(algorithm:, key:)
raise ArgumentError, "algorithm=#{algorithm.inspect}" unless algorithm.is_a?(String)
Signer.new(algorithm, key)
end
end
class Token
def sign!(key:, algorithm:)
JWA.create_signer(algorithm: algorithm, key: key).tap do |signer|
@signature = signer.key
end
nil
end
end
100.times do |i|
Token.new.sign!(algorithm: 'ES256', key: :the_key)
rescue ArgumentError => e
puts "FAIL at #{i}: #{e.message}"; exit 1
end
puts 'OK'
Results:
$ ruby test.rb # 4.0.7, interpreter
OK
$ ruby --yjit test.rb # 4.0.7
OK
$ ruby --zjit test.rb # 4.0.7
FAIL at 29: algorithm=:the_key
$ ./miniruby --zjit test.rb # master 46033eff6c (2026-10-11)
OK
The bug needs both conditions. With the same keyword order on both sides, or without the block in sign!, the script prints OK on 4.0.7 with --zjit too.
This looks like the bug reported in https://github.com/Shopify/ruby/issues/922 and fixed on master by https://github.com/ruby/ruby/pull/15827 ("ZJIT: Spill stack from direct send with args in callee order", commit d81a11d4e61f67b6fb0aaa44aaa7ead4022148dd). That commit is not in v4.0.7, and I could not find it on the ruby_4_0 branch.
Could d81a11d4e6 be backported to Ruby 4.0? The jwt failure was seen on x86_64-linux; the minimal reproduction above was run on arm64-darwin (ruby 4.0.7 +PRISM [arm64-darwin27]). Thank you!
No data to display