[perl #81944] Non-lvalue subs do not copy return values
authorFather Chrysostomos <sprout@cpan.org>
Thu, 16 Jun 2011 13:27:50 +0000 (06:27 -0700)
committerFather Chrysostomos <sprout@cpan.org>
Fri, 17 Jun 2011 03:17:52 +0000 (20:17 -0700)
return and leavesub see if they can cheat by not copying anything
marked TEMP, since presumably nothing else is using it.  That means
the return values of delete() and shift() are not copied.  Since @_
aliases to the caller’s variables, sometimes what is returned *is*
used elsewhere and still marked TEMP.  So cases like sub { return
delete $_[0] } ->($x) end up returning $x unchanged, instead of
copying it.

As mentioned in the ticket, the solution is to copy only if the refer-
ence count is 1.

This also allows me to simplify the lvalue-returning code without
spreading this bug further.  (pp_leavesublv currently avoids calling
sv_2mortal, in order not to set the TEMP flag.)

pp_ctl.c
pp_hot.c
t/op/sub.t

index c600a91..303e356 100644 (file)
--- a/pp_ctl.c
+++ b/pp_ctl.c
@@ -2352,7 +2352,7 @@ PP(pp_return)
        if (MARK < SP) {
            if (popsub2) {
                if (cx->blk_sub.cv && CvDEPTH(cx->blk_sub.cv) > 1) {
-                   if (SvTEMP(TOPs)) {
+                   if (SvTEMP(TOPs) && SvREFCNT(TOPs) == 1) {
                        *++newsp = SvREFCNT_inc(*SP);
                        FREETMPS;
                        sv_2mortal(*newsp);
@@ -2365,7 +2365,7 @@ PP(pp_return)
                        if (gmagic) SvGETMAGIC(sv);
                    }
                }
-               else if (SvTEMP(*SP)) {
+               else if (SvTEMP(*SP) && SvREFCNT(*SP) == 1) {
                    *++newsp = *SP;
                    if (gmagic) SvGETMAGIC(*SP);
                }
@@ -2380,7 +2380,7 @@ PP(pp_return)
       }
       else if (gimme == G_ARRAY) {
        while (++MARK <= SP) {
-           *++newsp = popsub2 && SvTEMP(*MARK)
+           *++newsp = popsub2 && SvTEMP(*MARK) && SvREFCNT(*MARK) == 1
                        ? *MARK : sv_mortalcopy(*MARK);
            TAINT_NOT;          /* Each item is independent */
        }
index f1c4977..b2970d8 100644 (file)
--- a/pp_hot.c
+++ b/pp_hot.c
@@ -2611,7 +2611,7 @@ PP(pp_leavesub)
        MARK = newsp + 1;
        if (MARK <= SP) {
            if (cx->blk_sub.cv && CvDEPTH(cx->blk_sub.cv) > 1) {
-               if (SvTEMP(TOPs)) {
+               if (SvTEMP(TOPs) && SvREFCNT(TOPs) == 1) {
                    *MARK = SvREFCNT_inc(TOPs);
                    FREETMPS;
                    sv_2mortal(*MARK);
@@ -2624,7 +2624,7 @@ PP(pp_leavesub)
                    SvREFCNT_dec(sv);
                }
            }
-           else if (SvTEMP(TOPs)) {
+           else if (SvTEMP(TOPs) && SvREFCNT(TOPs) == 1) {
                *MARK = TOPs;
                if (gmagic) SvGETMAGIC(TOPs);
            }
@@ -2639,7 +2639,7 @@ PP(pp_leavesub)
     }
     else if (gimme == G_ARRAY) {
        for (MARK = newsp + 1; MARK <= SP; MARK++) {
-           if (!SvTEMP(*MARK)) {
+           if (!SvTEMP(*MARK) || SvREFCNT(*MARK) != 1) {
                *MARK = sv_mortalcopy(*MARK);
                TAINT_NOT;      /* Each item is independent */
            }
index 5bd4508..b8e514d 100644 (file)
@@ -6,7 +6,7 @@ BEGIN {
     require './test.pl';
 }
 
-plan( tests => 8 );
+plan( tests => 14 );
 
 sub empty_sub {}
 
@@ -40,3 +40,27 @@ is(scalar(@test), 0, 'Didnt return anything');
     push @a, 34, 35, &{$x == $x};
     ok(eq_array(\@a, [34,35]), "yes without args");
 }
+
+# [perl #81944] return should always copy
+{
+    $foo{bar} = 7;
+    for my $x ($foo{bar}) {
+       # Pity test.pl doesnt have isn't.
+       isnt \sub { delete $foo{bar} }->(), \$x,
+          'result of delete(helem) is copied when returned';
+    }
+    $foo{bar} = 7;
+    for my $x ($foo{bar}) {
+       isnt \sub { return delete $foo{bar} }->(), \$x,
+          'result of delete(helem) is copied when explicitly returned';
+    }
+    my $x;
+    isnt \sub { delete $_[0] }->($x), \$x,
+      'result of delete(aelem) is copied when returned';
+    isnt \sub { return delete $_[0] }->($x), \$x,
+      'result of delete(aelem) is copied when explicitly returned';
+    isnt \sub { ()=\@_; shift }->($x), \$x,
+      'result of shift is copied when returned';
+    isnt \sub { ()=\@_; return shift }->($x), \$x,
+      'result of shift is copied when explicitly returned';
+}