Project

General

Profile

Bug #22335 ยป repro.rb

Reproduction script - eapache_opslevel (Evan Huus), 09/18/2026 03:02 PM

 
# Exit status: 0 clean, 1 corruption, 2 unlucky hash salt (rerun).

module Ref
# Even though the refinement is lexically scoped to the module, it globally disables the interpreter's ability to do
# fast-path integer comparisons.
refine Integer do
def ==(other) = super
end
end

N = 128 # must exceed 31; higher makes reproducing easier

# Figure out two argument indices:
# - above index 31 so they use the hash path
# - which collide in the 8-bit ar_hint that small hashes use for the linear scan
hint = ->(i) { i.hash & 0xff }
a, b = (0...N).to_a.combination(2).find { |x, y| hint[x] == hint[y] && y >= 31 }
if b.nil?
warn "no two keyword indices collide under this process's hash salt; rerun"
exit 2
end

# Only the two colliding indices get a call-time-evaluated default, so the Hash holds
# exactly those two keys; every other keyword gets a literal default, which is
# written into the locals once and never recomputed.
params = (0...N).map { |i| [a, b].include?(i) ? "k#{i}: {}" : "k#{i}: #{i}" }
eval "def victim(#{params.join(', ')}) = [#{(0...N).map { |i| "k#{i}" }.join(', ')}]"

# The victim method just returns its arguments, which *should* all be consecutive integers
# except for the two collision candidates which should be {}
got = victim
bad = (0...N).reject { |i| [a, b].include?(i) || got[i] == i }

puts RUBY_DESCRIPTION
puts "colliding keyword indices #{a}/#{b}"
if bad.empty?
puts " clean"
else
bad.each { |i| puts " k#{i}: expected #{i}, got #{got[i].inspect[0, 48]}" }
end
exit(bad.empty? ? 0 : 1)
    (1-1/1)