Skip to content

Commit

Permalink
pp_tie should completely reset the underlying hash's iterator state.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nwc10 committed Aug 24, 2021
1 parent 8738e44 commit f25b5c2
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 0 deletions.
1 change: 1 addition & 0 deletions pp_sys.c
Expand Up @@ -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:
Expand Down
78 changes: 78 additions & 0 deletions t/op/tiehash.t
Expand Up @@ -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();

0 comments on commit f25b5c2

Please sign in to comment.