This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Perl_sv_vcatpvfn_flags: remove "%.Ng" special-case
[perl5.git] / sv.c
diff --git a/sv.c b/sv.c
index 073df74..0520ac0 100644 (file)
--- a/sv.c
+++ b/sv.c
@@ -11036,29 +11036,6 @@ S_F0convert(NV nv, char *const endbuf, STRLEN *const len)
 }
 
 
-/*
-=for apidoc sv_vcatpvfn
-
-=for apidoc sv_vcatpvfn_flags
-
-Processes its arguments like C<vsprintf> and appends the formatted output
-to an SV.  Uses an array of SVs if the C-style variable argument list is
-missing (C<NULL>). Argument reordering (using format specifiers like C<%2$d>
-or C<%*2$d>) is supported only when using an array of SVs; using a C-style
-C<va_list> argument list with a format string that uses argument reordering
-will yield an exception.
-
-When running with taint checks enabled, indicates via
-C<maybe_tainted> if results are untrustworthy (often due to the use of
-locales).
-
-If called as C<sv_vcatpvfn> or flags has the C<SV_GMAGIC> bit set, calls get magic.
-
-Usually used via one of its frontends C<sv_vcatpvf> and C<sv_vcatpvf_mg>.
-
-=cut
-*/
-
 #define VECTORIZE_ARGS vecsv = va_arg(*args, SV*);\
                        vecstr = (U8*)SvPV_const(vecsv,veclen);\
                        vec_utf8 = DO_UTF8(vecsv);
@@ -11767,9 +11744,32 @@ S_format_hexfp(pTHX_ char * const buf, const STRLEN bufsize, const char c,
 void
 
 
-/* This function assumes that pat has the same utf8-ness as sv.
- * It's the caller's responsibility to ensure that this is so.
- */
+/*
+=for apidoc sv_vcatpvfn
+
+=for apidoc sv_vcatpvfn_flags
+
+Processes its arguments like C<vsprintf> and appends the formatted output
+to an SV.  Uses an array of SVs if the C-style variable argument list is
+missing (C<NULL>). Argument reordering (using format specifiers like C<%2$d>
+or C<%*2$d>) is supported only when using an array of SVs; using a C-style
+C<va_list> argument list with a format string that uses argument reordering
+will yield an exception.
+
+When running with taint checks enabled, indicates via
+C<maybe_tainted> if results are untrustworthy (often due to the use of
+locales).
+
+If called as C<sv_vcatpvfn> or flags has the C<SV_GMAGIC> bit set, calls get magic.
+
+It assumes that pat has the same utf8-ness as sv.  It's the caller's
+responsibility to ensure that this is so.
+
+Usually used via one of its frontends C<sv_vcatpvf> and C<sv_vcatpvf_mg>.
+
+=cut
+*/
+
 
 Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN patlen,
                        va_list *const args, SV **const svargs, const I32 svmax, bool *const maybe_tainted,
@@ -11790,7 +11790,6 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
      * Plus 32: Playing safe. */
     char ebuf[IV_DIG * 4 + NV_DIG + 32];
     bool no_redundant_warning = FALSE; /* did we use any explicit format parameter index? */
-    bool hexfp = FALSE; /* hexadecimal floating point? */
 
     DECLARATION_FOR_LC_NUMERIC_MANIPULATION;
 
@@ -11803,6 +11802,11 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
     /* no matter what, this is a string now */
     (void)SvPV_force_nomg(sv, origlen);
 
+    /* the code that scans for flags etc following a % relies on
+     * a '\0' being present to avoid falling off the end. Ideally that
+     * should be fixed */
+    assert(pat[patlen] == '\0');
+
     /* special-case "", "%s", and "%-p" (SVf - see below) */
     if (patlen == 0) {
        if (svmax && ckWARN(WARN_REDUNDANT))
@@ -11839,42 +11843,20 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
     }
 
 #if !defined(USE_LONG_DOUBLE) && !defined(USE_QUADMATH)
-    /* special-case "%.0f" and "%.<number>g" */
-    if ( !args && patlen <= 5 && pat[0] == '%' && pat[1] == '.'
-        && (pat[patlen-1] == 'g' || pat[patlen-1] == 'f') ) {
-       unsigned digits = 0;
-       const char *pp;
-
-       pp = pat + 2;
-       while (*pp >= '0' && *pp <= '9')
-           digits = 10 * digits + (*pp++ - '0');
-
-       /* XXX: Why do this `svix < svmax` test? Couldn't we just
-          format the first argument and WARN_REDUNDANT if svmax > 1?
-          Munged by Nicholas Clark in v5.13.0-209-g95ea86d */
-       if (pp + 1 == pat + patlen && svix < svmax) {
-           const NV nv = SvNV(*svargs);
-            if (LIKELY(!Perl_isinfnan(nv))) {
-                if (*pp == 'g') {
-                    /* Add check for digits != 0 because it seems that some
-                       gconverts are buggy in this case, and we don't yet have
-                       a Configure test for this.  */
-                    if (digits && digits < sizeof(ebuf) - NV_DIG - 10) {
-                        /* 0, point, slack */
-                        STORE_LC_NUMERIC_SET_TO_NEEDED();
-                        SNPRINTF_G(nv, ebuf, sizeof(ebuf), digits);
-                        sv_catpv_nomg(sv, ebuf);
-                        if (*ebuf)     /* May return an empty string for digits==0 */
-                            return;
-                    }
-                } else if (!digits) {
-                    STRLEN l;
+    /* special-case "%.0f" */
+    if (    !args
+         && patlen == 4
+         && pat[0] == '%' && pat[1] == '.' && pat[2] == '0' && pat[3] == 'f'
+         && svmax > 0)
+    {
+        const NV nv = SvNV(*svargs);
+        if (LIKELY(!Perl_isinfnan(nv))) {
+            STRLEN l;
+            char *p;
 
-                    if ((p = F0convert(nv, ebuf + sizeof ebuf, &l))) {
-                        sv_catpvn_nomg(sv, p, l);
-                        return;
-                    }
-                }
+            if ((p = F0convert(nv, ebuf + sizeof ebuf, &l))) {
+                sv_catpvn_nomg(sv, p, l);
+                return;
             }
        }
     }
@@ -11885,43 +11867,48 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
 
     patend = (char*)pat + patlen;
     for (p = (char*)pat; p < patend; p = q) {
-       bool alt = FALSE;
-       bool left = FALSE;
-       bool vectorize = FALSE;
-       bool vectorarg = FALSE;
-       bool vec_utf8 = FALSE;
-       char fill = ' ';
-       char plus = 0;
-       char intsize = 0;
-       STRLEN width = 0;
-       STRLEN zeros = 0;
-       bool has_precis = FALSE;
-       STRLEN precis = 0;
-       const I32 osvix = svix;
-       bool is_utf8 = FALSE;  /* is this item utf8?   */
-        bool used_explicit_ix = FALSE;
-        bool arg_missing = FALSE;
-       char esignbuf[4];
-       U8 utf8buf[UTF8_MAXBYTES+1];
-       STRLEN esignlen = 0;
-
-       const char *eptr = NULL;
-       const char *fmtstart;
-       STRLEN elen = 0;
-       SV *vecsv = NULL;
-       const U8 *vecstr = NULL;
-       STRLEN veclen = 0;
-       char c = 0;
-       unsigned base = 0;
-       IV iv = 0;
-       UV uv = 0;
-       const char *dotstr = ".";
-       STRLEN dotstrlen = 1;
-       I32 efix = 0; /* explicit format parameter index */
-       I32 ewix = 0; /* explicit width index */
-       I32 epix = 0; /* explicit precision index */
-       I32 evix = 0; /* explicit vector index */
-       bool asterisk = FALSE;
+
+       char intsize     = 0;         /* size qualifier in "%hi..." etc */
+       bool alt         = FALSE;     /* has      "%#..."    */
+       bool left        = FALSE;     /* has      "%-..."    */
+       char fill        = ' ';       /* has      "%0..."    */
+       char plus        = 0;         /* has      "%+..."    */
+       STRLEN width     = 0;         /* value of "%NNN..."  */
+       bool has_precis  = FALSE;     /* has      "%.NNN..." */
+       STRLEN precis    = 0;         /* value of "%.NNN..." */
+       bool asterisk    = FALSE;     /* has      "%*..."    */
+        bool used_explicit_ix = FALSE;/* has      "%$n..."   */
+       unsigned base    = 0;         /* base to print in, e.g. 8 for %o */
+       UV uv            = 0;         /* the value to print of int-ish args */
+       IV iv            = 0;         /* ditto for signed types */
+
+       bool vectorize   = FALSE;     /* has      "%v..."    */
+       bool vectorarg   = FALSE;     /* has      "%*v..."   */
+       SV *vecsv        = NULL;      /* the cur arg for %v  */
+       bool vec_utf8    = FALSE;     /* SvUTF8(vecsv)       */
+       const U8 *vecstr = NULL;      /* SvPVX(vecsv)        */
+       STRLEN veclen    = 0;         /* SvCUR(vecsv)        */
+       const char *dotstr = ".";     /* separator string for %v */
+       STRLEN dotstrlen = 1;         /* length of separator string for %v */
+
+       I32 efix         = 0;         /* explicit format parameter index */
+       I32 ewix         = 0;         /* explicit width index */
+       I32 epix         = 0;         /* explicit precision index */
+       I32 evix         = 0;         /* explicit vector index */
+       const I32 osvix  = svix;      /* original index in case of bad fmt */
+
+       bool is_utf8     = FALSE;     /* is this item utf8?   */
+        bool arg_missing = FALSE;     /* give "Missing argument" warning */
+       char esignbuf[4];             /* holds sign prefix, e.g. "-0x" */
+       STRLEN esignlen  = 0;         /* length of e.g. "-0x" */
+       STRLEN zeros     = 0;         /* how many '0' to prepend */
+
+       const char *eptr = NULL;      /* the address of the element string */
+       STRLEN elen      = 0;         /* the length  of the element string */
+
+       const char *fmtstart;         /* start of current format (the '%') */
+       char c           = 0;         /* current character read from format */
+
 
        /* echo everything up to the next format specification */
        for (q = p; q < patend && *q != '%'; ++q) ;
@@ -12317,9 +12304,11 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
            uv = (args) ? va_arg(*args, int) : SvIV_nomg(argsv);
            if ((uv > 255 ||
                 (!UVCHR_IS_INVARIANT(uv) && SvUTF8(sv)))
-               && !IN_BYTES) {
-               eptr = (char*)utf8buf;
-               elen = uvchr_to_utf8((U8*)eptr, uv) - utf8buf;
+               && !IN_BYTES)
+            {
+                assert(sizeof(ebuf) >= UTF8_MAXBYTES + 1);
+               eptr = ebuf;
+               elen = uvchr_to_utf8((U8*)eptr, uv) - (U8*)ebuf;
                is_utf8 = TRUE;
            }
            else {
@@ -12611,9 +12600,9 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
        case 'a': case 'A':
 
         {
-            bool   is_simple;  /* no fancy qualifiers */
             STRLEN radix_len;  /* SvCUR(PL_numeric_radix_sv) */
             STRLEN float_need; /* what PL_efloatsize needs to become */
+            bool hexfp;        /* hexadecimal floating point? */
 
             vcatpvfn_long_double_t fv;
             NV                     nv;
@@ -12709,15 +12698,15 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
             }
 
             /* special-case "%.0f" */
-            is_simple = ( !(width || left || plus || alt)
-                        && fill != '0'
-                        && has_precis
-                        && intsize != 'q');
-
-            if (is_simple && c == 'f' && !precis) {
-                if ((eptr = F0convert(nv, ebuf + sizeof ebuf, &elen)))
-                    break;
-            }
+            if (   c == 'f'
+                && !precis
+                && has_precis
+                && !(width || left || plus || alt)
+                && fill != '0'
+                && intsize != 'q'
+                && ((eptr = F0convert(nv, ebuf + sizeof ebuf, &elen)))
+            )
+                goto float_concat_no_utf8;
 
             /* Determine the buffer size needed for the various
              * floating-point formats.
@@ -12778,6 +12767,8 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
              */
            float_need += radix_len;
 
+            hexfp = FALSE;
+
            if (isALPHA_FOLD_EQ(c, 'f')) {
                 /* Determine how many digits before the radix point
                  * might be emitted.  frexp() (or frexpl) has some
@@ -12829,6 +12820,26 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
                     float_need += digits;
                 }
            }
+            /* special-case "%.<number>g" if it will fit in ebuf */
+            else if (c == 'g'
+                && precis   /* See earlier comment about buggy Gconvert
+                               when digits, aka precis, is 0  */
+                && has_precis
+                /* check, in manner not involving wrapping, that it will
+                 * fit in ebuf  */
+                && float_need < sizeof(ebuf)
+                && sizeof(ebuf) - float_need > precis
+                && !(width || left || plus || alt)
+                && fill != '0'
+                && intsize != 'q'
+            ) {
+                STORE_LC_NUMERIC_SET_TO_NEEDED();
+                SNPRINTF_G(fv, ebuf, sizeof(ebuf), precis);
+                elen = strlen(ebuf);
+                eptr = ebuf;
+                goto float_concat;
+           }
+
 
             {
                 STRLEN pr = has_precis ? precis : 6; /* known default */
@@ -12859,21 +12870,6 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
                PL_efloatbuf[0] = '\0';
            }
 
-            /* special-case "%.<number>g" */
-            if (is_simple) {
-               /* See earlier comment about buggy Gconvert when digits,
-                  aka precis is 0  */
-               if ( c == 'g' && precis ) {
-                    STORE_LC_NUMERIC_SET_TO_NEEDED();
-                    SNPRINTF_G(fv, PL_efloatbuf, PL_efloatsize, precis);
-                   /* May return an empty string for digits==0 */
-                   if (*PL_efloatbuf) {
-                       elen = strlen(PL_efloatbuf);
-                       goto float_converted;
-                   }
-                }
-           }
-
             if (UNLIKELY(hexfp)) {
                 elen = S_format_hexfp(aTHX_ PL_efloatbuf, PL_efloatsize, c,
                                 nv, fv, has_precis, precis, width,
@@ -12955,12 +12951,51 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
                 GCC_DIAG_RESTORE;
            }
 
-       float_converted:
            eptr = PL_efloatbuf;
-            assert((IV)elen > 0); /* here zero elen is bad */
 
+         float_concat:
 
-           break;
+            /* Since floating-point formats do their own formatting and
+             * padding, we skip the main block of code at the end of this
+             * loop which handles appending eptr to sv, and do our own
+             * stripped-down version */
+
+            /* floating-point formats only get is_utf8 if the radix point
+             * is utf8. All other characters in the string are < 128
+             * and so can be safely appended to both a non-utf8 and utf8
+             * string as-is.
+             */
+            if (is_utf8 && !has_utf8) {
+                sv_utf8_upgrade(sv);
+                has_utf8 = TRUE;
+            }
+
+         float_concat_no_utf8:
+
+            assert(!zeros);
+            assert(!esignlen);
+            assert(!vectorize);
+            assert(elen);
+            assert(elen >= width);
+
+
+            {
+                /* unrolled Perl_sv_catpvn */
+                STRLEN need = elen + SvCUR(sv) + 1;
+                char *end;
+                /* can't wrap as both elen and SvCUR() are allocated in
+                 * memory and together can't consume all the address space
+                 */
+                assert(need > elen);
+                SvGROW(sv, need);
+                end = SvEND(sv);
+                Copy(eptr, end, elen, char);
+                end += elen;
+                *end = '\0';
+                SvCUR_set(sv, need - 1);
+            }
+
+            goto donevalidconversion;
         }
 
            /* SPECIAL */