Skip to content

Hash#rehash corrupts insertion-order when deduplicating keys #9340

@kares

Description

@kares

Hash#rehash corrupts the internal insertion-order doubly-linked list when it deduplicates keys (i.e., when mutated keys now compare as equal). After the corrupted rehash, inserting new entries disconnects existing entries from iteration, and calling h.keys can trigger a NullPointerException.

Root Cause

In RubyHash#rehash, when a duplicate key is found and its entry is unlinked from the insertion-order list:

RubyHashEntry tmpNext = entry.nextAdded;
RubyHashEntry tmpPrev = entry.prevAdded;
tmpPrev.nextAdded = tmpNext;    // correct: prev.next skips over entry
tmpPrev.prevAdded = tmpPrev;    // BUG: should be tmpNext.prevAdded = tmpPrev

Reproducer

a = [1]; b = [2]
h = { a => "a", b => "b" }
a[0] = 2
h.rehash
h[[3]] = "c"

expected = [[2], [3]]
actual = []
h.each { |k, v| actual << k }

if actual == expected
  puts "PASS: each yields #{actual.inspect}"
else
  puts "FAIL: each yields #{actual.inspect}, expected #{expected.inspect}"
end

# h.keys.inspect triggers NullPointerException on JRuby
# puts h.keys.inspect

CRuby 3.4.8: PASS: each yields [[2], [3]]
JRuby (master): FAIL: each yields [[3]], expected [[2], [3]]

Uncommenting h.keys.inspect produces:

Unhandled Java exception: java.lang.NullPointerException: Cannot read field "metaClass" because "arg" is null

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions