[前][次][番号順一覧][スレッド一覧]

ruby-changes:31902

From: nobu <ko1@a...>
Date: Tue, 3 Dec 2013 22:32:32 +0900 (JST)
Subject: [ruby-changes:31902] nobu:r43981 (trunk): hash.c: same hash value for similar constructs

nobu	2013-12-03 22:32:24 +0900 (Tue, 03 Dec 2013)

  New Revision: 43981

  http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=43981

  Log:
    hash.c: same hash value for similar constructs
    
    * hash.c (rb_hash_recursive): make similar (recursive) constructs
      return same hash value.  execute recursively, and rewind to the
      topmost frame with an object which .eql? to the recursive
      object, if recursion is detected.

  Modified files:
    trunk/ChangeLog
    trunk/hash.c
    trunk/test/ruby/test_array.rb
    trunk/test/ruby/test_thread.rb
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 43980)
+++ ChangeLog	(revision 43981)
@@ -1,4 +1,9 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1
-Tue Dec  3 22:18:27 2013  Nobuyoshi Nakada  <nobu@r...>
+Tue Dec  3 22:32:18 2013  Nobuyoshi Nakada  <nobu@r...>
+
+	* hash.c (rb_hash_recursive): make similar (recursive) constructs
+	  return same hash value.  execute recursively, and rewind to the
+	  topmost frame with an object which .eql? to the recursive
+	  object, if recursion is detected.
 
 	* hash.c (rb_hash): detect recursion for all `hash' methods.  each
 	  `hash' methods no longer need to use rb_exec_recursive().
Index: hash.c
===================================================================
--- hash.c	(revision 43980)
+++ hash.c	(revision 43981)
@@ -48,7 +48,7 @@ rb_hash_freeze(VALUE hash) https://github.com/ruby/ruby/blob/trunk/hash.c#L48
 VALUE rb_cHash;
 
 static VALUE envtbl;
-static ID id_hash, id_yield, id_default;
+static ID id_hash, id_yield, id_default, id_recursive_hash;
 
 VALUE
 rb_hash_set_ifnone(VALUE hash, VALUE ifnone)
@@ -76,6 +76,71 @@ rb_any_cmp(VALUE a, VALUE b) https://github.com/ruby/ruby/blob/trunk/hash.c#L76
     return !rb_eql(a, b);
 }
 
+struct hash_recursive_args {
+    VALUE (*func)(VALUE, VALUE, int);
+    VALUE obj;
+    VALUE arg;
+};
+
+static VALUE
+call_hash(RB_BLOCK_CALL_FUNC_ARGLIST(tag, data))
+{
+    struct hash_recursive_args *p = (struct hash_recursive_args *)tag;
+    return p->func(p->obj, p->arg, 0);
+}
+
+static VALUE
+rb_hash_recursive(VALUE (*func)(VALUE, VALUE, int), VALUE obj, VALUE arg)
+{
+    VALUE hval;
+    VALUE thread = rb_thread_current();
+    VALUE recursion_list = rb_thread_local_aref(thread, id_recursive_hash);
+    struct hash_recursive_args args;
+    long len = 0;
+    int state;
+
+    if (!NIL_P(recursion_list) && (len = RARRAY_LEN(recursion_list)) > 0) {
+	VALUE outer;
+	struct hash_recursive_args *outerp;
+	const VALUE *ptr = RARRAY_CONST_PTR(recursion_list);
+	long i = 0;
+	do {
+	    outerp = (struct hash_recursive_args *)(ptr[i] & ~FIXNUM_FLAG);
+	    if (outerp->obj == obj) {
+		len = i;
+		for (i = 0; i < len; ++i) {
+		    outer = RARRAY_AREF(recursion_list, i) & ~FIXNUM_FLAG;
+		    outerp = (struct hash_recursive_args *)outer;
+		    if (rb_eql(obj, outerp->obj)) {
+			rb_throw_obj(outer, outer);
+		    }
+		}
+		outer = RARRAY_AREF(recursion_list, len) & ~FIXNUM_FLAG;
+		outerp = (struct hash_recursive_args *)outer;
+		rb_throw_obj(outer, outer);
+	    }
+	} while (++i < len);
+    }
+    else {
+	recursion_list = rb_ary_new();
+	rb_thread_local_aset(thread, id_recursive_hash, recursion_list);
+    }
+    args.func = func;
+    args.obj = obj;
+    args.arg = arg;
+    rb_ary_push(recursion_list, (VALUE)&args | FIXNUM_FLAG);
+    hval = rb_catch_protect((VALUE)&args, call_hash, (VALUE)&args, &state);
+    if (RARRAY_LEN(recursion_list) >= len) {
+	rb_ary_set_len(recursion_list, len);
+	if (len == 0) rb_thread_local_aset(thread, id_recursive_hash, Qnil);
+    }
+    if (state) rb_jump_tag(state);
+    if (hval == (VALUE)&args) {
+	hval = (*func)(obj, arg, 1);
+    }
+    return hval;
+}
+
 static VALUE
 hash_recursive(VALUE obj, VALUE arg, int recurse)
 {
@@ -89,7 +154,7 @@ hash_recursive(VALUE obj, VALUE arg, int https://github.com/ruby/ruby/blob/trunk/hash.c#L154
 VALUE
 rb_hash(VALUE obj)
 {
-    VALUE hval = rb_exec_recursive_paired(hash_recursive, obj, obj, 0);
+    VALUE hval = rb_hash_recursive(hash_recursive, obj, 0);
   retry:
     switch (TYPE(hval)) {
       case T_FIXNUM:
Index: test/ruby/test_array.rb
===================================================================
--- test/ruby/test_array.rb	(revision 43980)
+++ test/ruby/test_array.rb	(revision 43981)
@@ -2053,6 +2053,7 @@ class TestArray < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_array.rb#L2053
     assert_not_equal([[1]].hash, [[2]].hash)
     a = []
     a << a
+    assert_equal([[a]].hash, a.hash)
     assert_not_equal([a, 1].hash, [a, 2].hash)
     assert_not_equal([a, a].hash, a.hash) # Implementation dependent
   end
Index: test/ruby/test_thread.rb
===================================================================
--- test/ruby/test_thread.rb	(revision 43980)
+++ test/ruby/test_thread.rb	(revision 43981)
@@ -467,6 +467,19 @@ class TestThread < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_thread.rb#L467
     m.unlock
   end
 
+  def test_recursive_outer
+    arr = []
+    obj = Struct.new(:foo, :visited).new(arr, false)
+    arr << obj
+    def obj.hash
+      self[:visited] = true
+      super
+      raise "recursive_outer should short circuit intermediate calls"
+    end
+    assert_nothing_raised {arr.hash}
+    assert(obj[:visited], "obj.hash was not called")
+  end
+
   def test_thread_instance_variable
     bug4389 = '[ruby-core:35192]'
     assert_in_out_err([], <<-INPUT, %w(), [], bug4389)

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]