Fix bug [perl #24380] : assigning to a hash in list
Rafael Garcia-Suarez [Thu, 13 Nov 2003 18:55:37 +0000 (18:55 +0000)]
or scalar context yielded a wrong value if the list
contained duplicated keys for the hash. This is fixed
by counting the number of duplicate keys and trimming
the stack by the corresponding number of items.

p4raw-id: //depot/perl@21714

pp_hot.c
t/op/hashassign.t

index 8f2f3e0..5f23bb3 100644 (file)
--- a/pp_hot.c
+++ b/pp_hot.c
@@ -966,8 +966,11 @@ PP(pp_aassign)
     HV *hash;
     I32 i;
     int magic;
+    int duplicates = 0;
+    SV **firsthashrelem;
 
     PL_delaymagic = DM_DELAY;          /* catch simultaneous items */
+    gimme = GIMME_V;
 
     /* If there's a common identifier on both sides we have to take
      * special care that assigning the identifier on the left doesn't
@@ -1021,6 +1024,7 @@ PP(pp_aassign)
                hash = (HV*)sv;
                magic = SvMAGICAL(hash) != 0;
                hv_clear(hash);
+               firsthashrelem = relem;
 
                while (relem < lastrelem) {     /* gobble up all the rest */
                    HE *didstore;
@@ -1032,6 +1036,9 @@ PP(pp_aassign)
                    if (*relem)
                        sv_setsv(tmpstr,*relem);        /* value */
                    *(relem++) = tmpstr;
+                   if (gimme != G_VOID && hv_exists_ent(hash, sv, 0))
+                       /* key overwrites an existing entry */
+                       duplicates += 2;
                    didstore = hv_store_ent(hash,sv,tmpstr,0);
                    if (magic) {
                        if (SvSMAGICAL(tmpstr))
@@ -1132,17 +1139,26 @@ PP(pp_aassign)
     }
     PL_delaymagic = 0;
 
-    gimme = GIMME_V;
     if (gimme == G_VOID)
        SP = firstrelem - 1;
     else if (gimme == G_SCALAR) {
        dTARGET;
        SP = firstrelem;
-       SETi(lastrelem - firstrelem + 1);
+       SETi(lastrelem - firstrelem + 1 - duplicates);
     }
     else {
-       if (ary || hash)
+       if (ary)
            SP = lastrelem;
+       else if (hash) {
+           if (duplicates) {
+               /* Removes from the stack the entries which ended up as
+                * duplicated keys in the hash (fix for [perl #24380]) */
+               Move(firsthashrelem + duplicates,
+                       firsthashrelem, duplicates, SV**);
+               lastrelem -= duplicates;
+           }
+           SP = lastrelem;
+       }
        else
            SP = firstrelem + (lastlelem - firstlelem);
        lelem = firstlelem + (relem - firstrelem);
index a1c66c3..7058d75 100644 (file)
@@ -8,7 +8,7 @@ BEGIN {
 
 # use strict;
 
-plan tests => 206;
+plan tests => 213;
 
 my @comma = ("key", "value");
 
@@ -272,4 +272,20 @@ foreach my $chr (60, 200, 600, 6000, 60000) {
 
 }
 
-
+# now some tests for hash assignment in scalar and list context with
+# duplicate keys [perl #24380]
+{
+    my %h; my $x; my $ar;
+    is( (join ':', %h = (1) x 8), '1:1',
+       'hash assignment in list context removes duplicates' );
+    is( scalar( %h = (1,2,1,3,1,4,1,5) ), 2,
+       'hash assignment in scalar context' );
+    is( scalar( ($x,%h) = (0,1,2,1,3,1,4,1,5) ), 3,
+       'scalar + hash assignment in scalar context' );
+    $ar = [ %h = (1,2,1,3,1,4,1,5) ];
+    is( $#$ar, 1, 'hash assignment in list context' );
+    is( "@$ar", "1 5", '...gets the last values' );
+    $ar = [ ($x,%h) = (0,1,2,1,3,1,4,1,5) ];
+    is( $#$ar, 2, 'scalar + hash assignment in list context' );
+    is( "@$ar", "0 1 5", '...gets the last values' );
+}