This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Free tied hash iterator state immediately at the `untie` call
authorNicholas Clark <nick@ccl4.org>
Tue, 24 Aug 2021 16:15:51 +0000 (16:15 +0000)
committerNicholas Clark <nick@ccl4.org>
Wed, 22 Sep 2021 06:48:45 +0000 (06:48 +0000)
Previously the state was only freed at the point when the hash was iterated
again, or re-tied, or destroyed.

This shouldn't make any difference to sane code, but the change can be
detected with suitably pathological pure-Perl code, so someone might just
(unwisely) be relying on this undocumented implementation detail.

pod/perldelta.pod
pp_sys.c
t/op/tie.t

index 8295a36..f888515 100644 (file)
@@ -363,7 +363,39 @@ files in F<ext/> and F<lib/> are best summarized in L</Modules and Pragmata>.
 
 =item *
 
-XXX
+Calling C<untie> on a tied hash that is partway through iteration now frees the
+iteration state immediately.
+
+Iterating a tied hash causes perl to store a copy of the current hash key to
+track the iteration state, with this stored copy passed as the second parameter
+to C<NEXTKEY>. This internal state is freed immediately when tie hash iteration
+completes, or if the hash is destroyed, but due to an implementation oversight,
+it was not freed if the hash was untied. In that case, the internal copy of the
+key would persist until the earliest of
+
+=over 4
+
+=item 1
+
+C<tie> was called again on the same hash
+
+=item 2
+
+The (now untied) hash was iterated (ie passed to any of C<keys>, C<values> or
+C<each>)
+
+=item 3
+
+The hash was destroyed.
+
+=back
+
+This inconsistency is now fixed - the internal state is now freed immediately by
+C<untie>.
+
+As the precise timing of this behaviour can be observed with pure Perl code
+(the timing of C<DESTROY> on objects returned from C<FIRSTKEY> and C<NEXTKEY>)
+it's just possible that some code is sensitive to it.
 
 =back
 
index cb449c3..5f6370d 100644 (file)
--- a/pp_sys.c
+++ b/pp_sys.c
@@ -1033,6 +1033,18 @@ PP(pp_untie)
         }
     }
     sv_unmagic(sv, how) ;
+
+    if (SvTYPE(sv) == SVt_PVHV) {
+        /* If the tied hash was partway through iteration, free the iterator and
+         * any key that it is pointing to. */
+        HE *entry;
+        if (HvLAZYDEL(sv) && (entry = HvEITER_get((HV *)sv))) {
+            HvLAZYDEL_off(sv);
+            hv_free_ent((HV *)sv, entry);
+            HvEITER_set(MUTABLE_HV(sv), 0);
+        }
+    }
+
     RETPUSHYES;
 }
 
index 68d42ee..089c1ee 100644 (file)
@@ -1697,7 +1697,7 @@ FIRSTKEY is Note 0
 Destroying 0
 NEXTKEY is Note 1
 Before untie
+Destroying 1
 After untie
 Before regular iteration
-Destroying 1
 After regular iteration