From: "incubus (Alexander Logvinov) via ruby-core" Date: 2026-10-11T10:34:41+00:00 Subject: [ruby-core:127067] [Ruby Bug#22426] ZJIT: keyword arguments swapped when passed out of declared order to a method with a block (please backport d81a11d4e6 to 4.0) Issue #22426 has been reported by incubus (Alexander Logvinov). ---------------------------------------- Bug #22426: ZJIT: keyword arguments swapped when passed out of declared order to a method with a block (please backport d81a11d4e6 to 4.0) https://bugs.ruby-lang.org/issues/22426 * Author: incubus (Alexander Logvinov) * Status: Open * ruby -v: ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [x86_64-linux] * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- 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): ``` ruby # 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! -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/