From 32ba66183606ed8d4c6841a5070466cb7a53fc3e Mon Sep 17 00:00:00 2001 From: Jeremy Thurgood Date: Sat, 2 Mar 2013 21:37:24 +0200 Subject: [PATCH 1/2] Handle recursion in Array#hash. --- lib-topaz/array.rb | 26 +++++++++++++++++++++----- spec/tags/core/array/hash_tags.txt | 6 +----- topaz/objects/threadobject.py | 6 ++++++ 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/lib-topaz/array.rb b/lib-topaz/array.rb index d1ea4eea9..3c6b8b84d 100644 --- a/lib-topaz/array.rb +++ b/lib-topaz/array.rb @@ -209,13 +209,29 @@ def eql?(other) return true end + def inner_hash(res) + 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) + res = 0x345678 + self.length + 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 end - return res end def *(arg) diff --git a/spec/tags/core/array/hash_tags.txt b/spec/tags/core/array/hash_tags.txt index 712062405..e84ab568e 100644 --- a/spec/tags/core/array/hash_tags.txt +++ b/spec/tags/core/array/hash_tags.txt @@ -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 diff --git a/topaz/objects/threadobject.py b/topaz/objects/threadobject.py index c3e7d7b12..aff8fc0d9 100644 --- a/topaz/objects/threadobject.py +++ b/topaz/objects/threadobject.py @@ -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 From c94823d645cbdcc60f8441d464028bf774b1ab78 Mon Sep 17 00:00:00 2001 From: Jeremy Thurgood Date: Sun, 3 Mar 2013 00:36:35 +0200 Subject: [PATCH 2/2] Explanatory comment. --- lib-topaz/array.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib-topaz/array.rb b/lib-topaz/array.rb index 3c6b8b84d..ee0504235 100644 --- a/lib-topaz/array.rb +++ b/lib-topaz/array.rb @@ -210,6 +210,10 @@ def eql?(other) 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. @@ -223,7 +227,13 @@ def inner_hash(res) private :inner_hash def 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