Stop pos from panicking when overloading changes UTF8ness
authorFather Chrysostomos <sprout@cpan.org>
Fri, 28 Sep 2012 04:29:33 +0000 (21:29 -0700)
committerFather Chrysostomos <sprout@cpan.org>
Mon, 1 Oct 2012 19:51:52 +0000 (12:51 -0700)
This touches on issues raised in tickets #114410 and #114690.

If the UTF8ness of an overloaded string changes with each call, it
will make magic_setpos panic if it tries to stringify the SV multiple
times.  We have to avoid any sv-specific utf8 length functions when
it comes to overloading.  And we should do the same thing for gmagic,
too, to avoid creating caches that will shortly be invalidated.

The test class is very closely based on code written by Nicholas Clark
in a response to #114410.

mg.c
sv.h
t/op/utf8cache.t

index db9b4ee..4ba96a4 100644 (file)
--- a/mg.c
+++ b/mg.c
@@ -2157,6 +2157,7 @@ Perl_magic_setpos(pTHX_ SV *sv, MAGIC *mg)
     STRLEN len;
     STRLEN ulen = 0;
     MAGIC* found;
+    const char *s;
 
     PERL_ARGS_ASSERT_MAGIC_SETPOS;
     PERL_UNUSED_ARG(mg);
@@ -2179,12 +2180,12 @@ Perl_magic_setpos(pTHX_ SV *sv, MAGIC *mg)
        found->mg_len = -1;
        return 0;
     }
-    len = SvPOK_nog(lsv) ? SvCUR(lsv) : sv_len(lsv);
+    s = SvPV_const(lsv, len);
 
     pos = SvIV(sv);
 
     if (DO_UTF8(lsv)) {
-       ulen = sv_len_utf8_nomg(lsv);
+       ulen = sv_or_pv_len_utf8(lsv, s, len);
        if (ulen)
            len = ulen;
     }
@@ -2198,7 +2199,7 @@ Perl_magic_setpos(pTHX_ SV *sv, MAGIC *mg)
        pos = len;
 
     if (ulen) {
-       pos = sv_pos_u2b_flags(lsv, pos, 0, 0);
+       pos = sv_or_pv_pos_u2b(lsv, s, pos);
     }
 
     found->mg_len = pos;
diff --git a/sv.h b/sv.h
index 18d3015..2ac6c50 100644 (file)
--- a/sv.h
+++ b/sv.h
@@ -1818,6 +1818,17 @@ mg.c:1024: warning: left-hand operand of comma expression has no effect
            sv_catsv_nomg(dsv, nsv);                    \
        } STMT_END
 
+#ifdef PERL_CORE
+# define sv_or_pv_len_utf8(sv, pv, bytelen)          \
+    (SvGAMAGIC(sv)                                    \
+       ? utf8_length((U8 *)(pv), (U8 *)(pv)+(bytelen)) \
+       : sv_len_utf8(sv))
+# define sv_or_pv_pos_u2b(sv, pv, pos)                  \
+    (SvGAMAGIC(sv)                                       \
+       ? (STRLEN)(utf8_hop((U8 *)(pv), pos) - (U8 *)(pv)) \
+       : sv_pos_u2b_flags(sv,pos,0,0))
+#endif
+
 /*
 =for apidoc Am|SV*|newRV_inc|SV* sv
 
index a9e88a6..2d10332 100644 (file)
@@ -9,7 +9,7 @@ BEGIN {
 
 use strict;
 
-plan(tests => 5);
+plan(tests => 7);
 
 SKIP: {
 skip_without_dynamic_extension("Devel::Peek");
@@ -77,3 +77,35 @@ sub {
   is ord substr($_[0], 1, 1), 0x100,
     'get-magic resets uf8cache on defelems';
 }->($h{k});
+
+
+# Overloading can also reallocate the PV.
+
+package UTF8Toggle {
+    use overload '""' => 'stringify', fallback => 1;
+
+    sub new {
+       my $class = shift;
+       my $value = shift;
+       my $state = shift||0;
+       return bless [$value, $state], $class;
+    }
+
+    sub stringify {
+       my $self = shift;
+       $self->[1] = ! $self->[1];
+       if ($self->[1]) {
+           utf8::downgrade($self->[0]);
+       } else {
+           utf8::upgrade($self->[0]);
+       }
+       $self->[0];
+    }
+}
+my $u = UTF8Toggle->new(" \x{c2}7 ");
+
+pos $u = 2;
+is pos $u, 2, 'pos on overloaded utf8 toggler';
+() = "$u"; # flip flag
+pos $u = 2;
+is pos $u, 2, 'pos on overloaded utf8 toggler (again)'