summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicholas Clark <nick@ccl4.org>2021-08-23 15:48:49 +0000
committerTony Cook <tony@develop-help.com>2021-08-25 14:41:55 +1000
commit364906c750e4f6e22ac4bb4d6b3742a1df87f976 (patch)
treedfcbbf27c3f94a44fc6e63c6055c91f89989585f
parent0f8b2f1e9307d973e41d064806756140723a2f31 (diff)
downloadperl-364906c750e4f6e22ac4bb4d6b3742a1df87f976.tar.gz
pp_tie should completely reset the underlying hash's iterator state.
Previously it would mangle it, resetting EITER but not RITER, meaning that after untie continuing iteration would be inconsistent - normally it would carry on exactly where it left off, but if iteration had been in the middle of a chain of HEs, it would skip the rest of the chain. Fixes GH #19077
-rw-r--r--pp_sys.c1
-rw-r--r--t/op/tiehash.t78
2 files changed, 79 insertions, 0 deletions
diff --git a/pp_sys.c b/pp_sys.c
index 2866959aa0..9b2d64ae2e 100644
--- a/pp_sys.c
+++ b/pp_sys.c
@@ -881,6 +881,7 @@ PP(pp_tie)
hv_free_ent((HV *)varsv, entry);
}
HvEITER_set(MUTABLE_HV(varsv), 0);
+ HvRITER_set(MUTABLE_HV(varsv), -1);
break;
}
case SVt_PVAV:
diff --git a/t/op/tiehash.t b/t/op/tiehash.t
index 6cce114e29..daa0c30c16 100644
--- a/t/op/tiehash.t
+++ b/t/op/tiehash.t
@@ -72,4 +72,82 @@ package TestIterators {
is(each %h, undef, "third iterator is undef");
}
+{
+ require Tie::Hash;
+
+ my %h = (
+ lolcat => "OH HAI!",
+ lolrus => "I HAS A BUCKET",
+ );
+
+ my @want = sort keys %h;
+
+ my @have;
+ while (1) {
+ my $k = each %h;
+ last
+ unless defined $k;
+ push @have, $k;
+ }
+ @have = sort @have;
+
+ # This is a sanity test:
+ is("@have", "@want", "get all keys from a loop");
+
+ @have = ();
+ keys %h;
+
+ my $k1 = each %h;
+
+ ok(defined $k1, "Got a key");
+
+ # no tie/untie here
+
+ while(1) {
+ my $k = each %h;
+ last
+ unless defined $k;
+ push @have, $k;
+ }
+
+ # As are these:
+ is(scalar @have, 1, "just 1 key from the loop this time");
+ isnt($k1, $have[0], "two different keys");
+
+ @have = sort @have, $k1;
+ is("@have", "@want", "get all keys just once");
+
+ # And this is the real test.
+ #
+ # Previously pp_tie would mangle the hash iterator state - it would reset
+ # EITER but not RITER, meaning that if the iterator happened to be partway
+ # down a chain of entries, the rest of that chain would be skipped, but if
+ # the iterator's next position was the start of a (new) chain, nothing would
+ # be skipped.
+ # We don't have space to store the complete older iterator state (and really
+ # nothing should be relying on it), so it seems better to correctly reset
+ # the iterator (every time), than leave it in a mess just occasionally.
+
+ @have = ();
+ keys %h;
+
+ my $k1 = each %h;
+
+ ok(defined $k1, "Got a key");
+
+ tie %h, 'Tie::StdHash';
+ untie %h;
+
+ while(1) {
+ my $k = each %h;
+ last
+ unless defined $k;
+ push @have, $k;
+ }
+
+ @have = sort @have;
+ is(scalar @have, 2, "2 keys from the loop this time");
+ is("@have", "@want", "tie/untie resets the hash iterator");
+}
+
done_testing();