summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog5
-rw-r--r--ext/-test-/st/numhash/extconf.rb1
-rw-r--r--ext/-test-/st/numhash/numhash.c63
-rw-r--r--st.c28
-rw-r--r--test/-ext-/st/test_numhash.rb21
5 files changed, 107 insertions, 11 deletions
diff --git a/ChangeLog b/ChangeLog
index 9b0668c..05c900f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+Tue Feb 7 14:29:16 2012 Nobuyoshi Nakada <nobu@ruby-lang.org>
+
+ * st.c (st_foreach): should not yield same pair when checking
+ after unpacking.
+
Mon Feb 6 21:55:13 2012 NARUSE, Yui <naruse@ruby-lang.org>
* tool/merger.rb: abort if the working directory is dirty.
diff --git a/ext/-test-/st/numhash/extconf.rb b/ext/-test-/st/numhash/extconf.rb
new file mode 100644
index 0000000..867fd75
--- /dev/null
+++ b/ext/-test-/st/numhash/extconf.rb
@@ -0,0 +1 @@
+create_makefile("-test-/st/numhash")
diff --git a/ext/-test-/st/numhash/numhash.c b/ext/-test-/st/numhash/numhash.c
new file mode 100644
index 0000000..3659100
--- /dev/null
+++ b/ext/-test-/st/numhash/numhash.c
@@ -0,0 +1,63 @@
+#include <ruby.h>
+#include <ruby/st.h>
+
+static VALUE
+numhash_alloc(VALUE klass)
+{
+ return Data_Wrap_Struct(klass, 0, 0, 0);
+}
+
+static VALUE
+numhash_init(VALUE self)
+{
+ st_table *tbl = (st_table *)DATA_PTR(self);
+ if (tbl) st_free_table(tbl);
+ DATA_PTR(self) = st_init_numtable();
+ return self;
+}
+
+static VALUE
+numhash_aref(VALUE self, VALUE key)
+{
+ st_data_t data;
+ if (!SPECIAL_CONST_P(key)) rb_raise(rb_eArgError, "not a special const");
+ if (st_lookup((st_table *)DATA_PTR(self), (st_data_t)key, &data))
+ return (VALUE)data;
+ return Qnil;
+}
+
+static VALUE
+numhash_aset(VALUE self, VALUE key, VALUE data)
+{
+ if (!SPECIAL_CONST_P(key)) rb_raise(rb_eArgError, "not a special const");
+ if (!SPECIAL_CONST_P(data)) rb_raise(rb_eArgError, "not a special const");
+ st_insert((st_table *)DATA_PTR(self), (st_data_t)key, (st_data_t)data);
+ return self;
+}
+
+static int
+numhash_i(st_data_t key, st_data_t value, st_data_t arg, int error)
+{
+ VALUE ret;
+ if (key == 0 && value == 0 && error == 1) rb_raise(rb_eRuntimeError, "numhash modified");
+ ret = rb_yield_values(3, (VALUE)key, (VALUE)value, (VALUE)arg);
+ if (ret == Qtrue) return ST_CHECK;
+ return ST_CONTINUE;
+}
+
+static VALUE
+numhash_each(VALUE self)
+{
+ return st_foreach((st_table *)DATA_PTR(self), numhash_i, self) ? Qtrue : Qfalse;
+}
+
+void
+Init_numhash(void)
+{
+ VALUE st = rb_define_class_under(rb_define_module("Bug"), "StNumHash", rb_cData);
+ rb_define_alloc_func(st, numhash_alloc);
+ rb_define_method(st, "initialize", numhash_init, 0);
+ rb_define_method(st, "[]", numhash_aref, 1);
+ rb_define_method(st, "[]=", numhash_aset, 2);
+ rb_define_method(st, "each", numhash_each, 0);
+}
diff --git a/st.c b/st.c
index d428530..215e27b 100644
--- a/st.c
+++ b/st.c
@@ -337,6 +337,9 @@ count_collision(const struct st_hash_type *type)
#define FOUND_ENTRY
#endif
+#define FIND_ENTRY(table, ptr, hash_val, bin_pos) \
+ ((ptr) = find_entry((table), key, (hash_val), ((bin_pos) = (hash_val)%(table)->num_bins)))
+
static st_table_entry *
find_entry(st_table *table, st_data_t key, st_index_t hash_val, st_index_t bin_pos)
{
@@ -420,8 +423,8 @@ st_get_key(st_table *table, register st_data_t key, st_data_t *result)
#define collision_check 1
static inline void
-add_direct(st_table * table, st_data_t key, st_data_t value,
- st_index_t hash_val, register st_index_t bin_pos)
+add_direct(st_table *table, st_data_t key, st_data_t value,
+ st_index_t hash_val, register st_index_t bin_pos)
{
register st_table_entry *entry;
if (table->num_entries > ST_DEFAULT_MAX_DENSITY * table->num_bins) {
@@ -819,7 +822,14 @@ st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg)
key = PKEY(table, i);
val = PVAL(table, i);
retval = (*func)(key, val, arg);
- if (!table->entries_packed) goto unpacked;
+ if (!table->entries_packed) {
+ FIND_ENTRY(table, ptr, key, i);
+ if (retval == ST_CHECK) {
+ if (!ptr) goto deleted;
+ goto unpacked_continue;
+ }
+ goto unpacked;
+ }
switch (retval) {
case ST_CHECK: /* check if hash is modified during iteration */
for (j = 0; j < table->num_entries; j++) {
@@ -827,9 +837,7 @@ st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg)
break;
}
if (j == table->num_entries) {
- /* call func with error notice */
- retval = (*func)(0, 0, arg, 1);
- return 1;
+ goto deleted;
}
/* fall through */
case ST_CONTINUE:
@@ -843,11 +851,6 @@ st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg)
}
}
return 0;
- unpacked:
- ptr = table->head;
- while (i-- > 0) {
- if (!(ptr = ptr->fore)) return 0;
- }
}
else {
ptr = table->head;
@@ -857,10 +860,12 @@ st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg)
do {
i = ptr->hash % table->num_bins;
retval = (*func)(ptr->key, ptr->record, arg);
+ unpacked:
switch (retval) {
case ST_CHECK: /* check if hash is modified during iteration */
for (tmp = table->bins[i]; tmp != ptr; tmp = tmp->next) {
if (!tmp) {
+ deleted:
/* call func with error notice */
retval = (*func)(0, 0, arg, 1);
return 1;
@@ -868,6 +873,7 @@ st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg)
}
/* fall through */
case ST_CONTINUE:
+ unpacked_continue:
ptr = ptr->fore;
break;
case ST_STOP:
diff --git a/test/-ext-/st/test_numhash.rb b/test/-ext-/st/test_numhash.rb
new file mode 100644
index 0000000..9ec072e
--- /dev/null
+++ b/test/-ext-/st/test_numhash.rb
@@ -0,0 +1,21 @@
+require 'test/unit'
+require "-test-/st/numhash"
+
+class Bug::StNumHash
+ class Test_NumHash < Test::Unit::TestCase
+ def setup
+ @tbl = Bug::StNumHash.new
+ 5.times {|i| @tbl[i] = i}
+ end
+
+ def test_check
+ keys = []
+ @tbl.each do |k, v, t|
+ keys << k
+ t[5] = 5 if k == 3
+ true
+ end
+ assert_equal([*0..5], keys)
+ end
+ end
+end