Skip to content

Recursive hashing corrupts shared hash buffer #7866

@notEthan

Description

@notEthan

I started memoizing the #hash method of a class (Ptr, below), which seems to have broken comparison of a Set containing instances of a class (Node) that has an ivar containing a Ptr, created in Node's initializer.

require 'set'

class Ptr
  def ==(other)
    other.is_a?(Ptr)
  end

  alias_method :eql?, :==

  def hash
    h = @hash ||= {x: 0}.hash
    puts "Ptr#hash:  #{h}"
    h
  end
end

class Node
  def initialize
    @ptr = Ptr.new
    @node_set = Set[self]
  end

  attr_reader :node_set

  def ==(other)
    other.is_a?(Node)
  end

  alias_method :eql?, :==

  def hash
    h = {ptr: @ptr}.hash
    puts "Node#hash: #{h}"
    h
  end
end

node = Node.new
post_node_set = Set[node]

puts
puts "post_node_set == node.node_set:"
puts  post_node_set == node.node_set
puts
puts "post_node_set.map(&:hash) == node.node_set.map(&:hash):"
puts  post_node_set.map(&:hash) == node.node_set.map(&:hash)
puts
puts "node.node_set == post_node_set:"
puts  node.node_set == post_node_set

Expected: all == comparisons result in true
Actual: first comparison is false (and the rest are true)

Some confluence of factors is at play here, I've simplified this down a lot, but simplifying further seems to no longer trigger the issue. Doing less in Node#initialize, or if Ptr#hash or Node#hash are hashing something simpler than Hash instances, result in expected behavior.

Output of the above, with notes:

Ptr#hash:  6413925280117835575
Node#hash: 8466601984141238220 ⬅️ from inside Node#initialize
Ptr#hash:  6413925280117835575
Node#hash: 3645463223149969145 ⬅️ after Node#initialize, should be same as from inside Node#initialize

post_node_set == node.node_set:
Ptr#hash:  6413925280117835575
Node#hash: 3645463223149969145
false                          ⬅️ expected: true

post_node_set.map(&:hash) == node.node_set.map(&:hash):
Ptr#hash:  6413925280117835575
Node#hash: 3645463223149969145
Ptr#hash:  6413925280117835575
Node#hash: 3645463223149969145
true

node.node_set == post_node_set:
Ptr#hash:  6413925280117835575
Node#hash: 3645463223149969145
true

Environment Information

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions