Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle recursion in Array#hash. #456

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions lib-topaz/array.rb
Original file line number Diff line number Diff line change
Expand Up @@ -209,13 +209,39 @@ def eql?(other)
return true
end

def inner_hash(res)
# This is the inner part of the hash value computation where we loop over
# our contents. If we find any recursion, we throw :array_hash_recursion to
# escape to the top level and ignore any hashing we've done inside the
# recursive array.
Thread.current.recursion_guard(self) do
self.each do |x|
# We want to keep this within a fixnum range.
res = Topaz.intmask((1000003 * res) ^ x.hash.to_int)
end
return res
end
throw :array_hash_recursion
end

private :inner_hash

def hash
res = 0x345678
self.each do |x|
# We want to keep this within a fixnum range.
res = Topaz.intmask((1000003 * res) ^ x.hash)
# Arrays of different lengths should hash to different values.
res = 0x345678 + self.length
# We need to stop calculating the hash value at the top level of a
# recursive array so that `a' and `[a]' (which are equal) have the same
# hash value. If we're in a recursion guard already, we assume there's
# already a suitable catch block higher up the stack. Otherwise we catch
# :array_hash_recursion and just return a length-based hash value.
if Thread.current.in_recursion_guard?
return self.inner_hash(res)
else
catch(:array_hash_recursion) do
return self.inner_hash(res)
end
return res
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand the logic here at all, at a minimum it needs an explanatory comment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, now I understand it, using throw/catch seems way too complex, why not just use the normal recursion_guard and if the object is in a recursion return 0 or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That catches the recursion on the inside rather than the outside, and we've already modified the hash for the intervening layers. We get a.hash == [[a]].hash because of the XOR, but a.hash != [a].hash.

end
return res
end

def *(arg)
Expand Down
6 changes: 1 addition & 5 deletions spec/tags/core/array/hash_tags.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
fails:properly handles recursive arrays
fails:returns the same hash for equal recursive arrays
fails:Array#hash calls to_int on result of calling hash on each element
fails:Array#hash ignores array class differences
fails:Array#hash returns same hash code for arrays with the same content
fails:Array#hash returns the same value if arrays are #eql?
fails:Array#hash
fails:Array#hash returns the same hash for equal recursive arrays through hashes
6 changes: 6 additions & 0 deletions topaz/objects/threadobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@ def method_recursion_guard(self, space, w_obj, block):
return space.w_true
space.invoke_block(block, [])
return space.w_false

@classdef.method("in_recursion_guard?")
def method_in_recursion_guardp(self, space):
if space.getexecutioncontext().recursive_objects:
return space.w_true
return space.w_false