This is a live mirror of the Perl 5 development currently hosted at
each() should not leave RITER set on empty hash
authorFather Chrysostomos <>
Sun, 6 Nov 2011 01:25:42 +0000 (18:25 -0700)
committerFather Chrysostomos <>
Sun, 6 Nov 2011 01:25:42 +0000 (18:25 -0700)
Commit 900ac0519e (5.11.0) sped up keys() on an empty hash by modify-
ing the iteration code not to loop through the buckets looking for an
entry if the number of keys is 0.  Interestingly, it had no visible
affect on keys(), but it *did* have one on each().  Resetting the ite-
rator’s current bucket number (RITER) used to be done inside that loop
in hv_iternext.  keys() always begins by resetting the iterator, so it
was unaffected.  But each %empty will leave the iterator as-is.  It
will be set on an empty hash if the last element was deleted while an
iterator was active.  This has buggy side-effects.

    $h{1} = 2;
    each %h;  # returns (1, 2)
    delete $h{1};
    each %h;  # returns false; should reset iterator
    print each %h, "\n";  # prints nothing

Commit 3b37eb248 (5.15.0), which changed the way S_hfreeentries works.
(S_hfreentries is called by all operators that empty hashes, such as
%h=() and undef %h.)  Now S_hfreentries does nothing if the hash is
empty.  That change on its own should have been harmless, but the
result was that even %h=() won’t reset RITER after each() has put
things in an inconsistent state.  This caused test failures in

So the solution, of course, is to complete the change made by
900ac0519e and reset the iterator properly in hv_iternext if the
hash is empty.


diff --git a/hv.c b/hv.c
index b5ee308..2ee24b4 100644 (file)
--- a/hv.c
+++ b/hv.c
@@ -2426,6 +2426,7 @@ Perl_hv_iternext_flags(pTHX_ HV *hv, I32 flags)
               or if we run through it and find only placeholders.  */
+    else iter->xhv_riter = -1;
     if (oldentry && HvLAZYDEL(hv)) {           /* was deleted earlier? */
index d9e1542..d12d678 100644 (file)
@@ -6,7 +6,7 @@ BEGIN {
     require './';
-plan tests => 56;
+plan tests => 57;
 $h{'abc'} = 'ABC';
 $h{'def'} = 'DEF';
@@ -255,3 +255,17 @@ for my $k (qw(each keys values)) {
     ::is($c, 1, "single key now freed");
+    # Make sure each() does not leave the iterator in an inconsistent state
+    # (RITER set to >= 0, with EITER null) if the active iterator is
+    # deleted, leaving the hash apparently empty.
+    my %h;
+    $h{1} = 2;
+    each %h;
+    delete $h{1};
+    each %h;
+    $h{1}=2;
+    is join ("-", each %h), '1-2',
+       'each on apparently empty hash does not leave RITER set';