This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Perl_sv_vcatpvfn_flags: sort PL_numeric_radix_sv
authorDavid Mitchell <davem@iabyn.com>
Mon, 15 May 2017 17:59:54 +0000 (18:59 +0100)
committerDavid Mitchell <davem@iabyn.com>
Wed, 7 Jun 2017 08:11:01 +0000 (09:11 +0100)
Under locales the radix point may not be just a simple '.' but a Unicode
string like "\N{ARABIC DECIMAL SEPARATOR}". Currently the hex f/p code
explicitly takes account of the length of this string when calculating the
buffer length, but the other branches don't - they just rely on the
"add 40 fudge factor" to protect them.

Instead, handle its length for all branches, and simplify utf8 handling.
Currently it checks post-format whether the radix point was utf8, and if
so marks the resulting buffer as utf8. Instead, check for utf8-ness at the
same time we check for length.

This new approach doesn't check whether the resulting string actually
contains the radix point string, so in principle the string could be
marked utf8 but not have any >127 chars. I think this is harmless.

sv.c

diff --git a/sv.c b/sv.c
index 7e2e39c..1f40c98 100644 (file)
--- a/sv.c
+++ b/sv.c
@@ -11597,6 +11597,8 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
        IV iv = 0;
        UV uv = 0;
         bool is_simple = TRUE; /* no fancy qualifiers */
+        STRLEN radix_len;  /* SvCUR(PL_numeric_radix_sv) */
+
        /* We need a long double target in case HAS_LONG_DOUBLE,
          * even without USE_LONG_DOUBLE, so that we can printf with
          * long double formats, even without NV being long double.
@@ -12420,7 +12422,25 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
                     break;
             }
 
-           float_need = 0;
+            /*determine the radix point len, e.g. length(".") in "1.2" */
+            radix_len  = 1; /* assume '.' */
+#ifdef USE_LOCALE_NUMERIC
+            /* note that we may either explicitly use PL_numeric_radix_sv
+             * below, or implicitly, via an snprintf() variant.
+             * Note also things like ps_AF.utf8 which has
+             * "\N{ARABIC DECIMAL SEPARATOR} as a radix point */
+            STORE_LC_NUMERIC_SET_TO_NEEDED();
+            if (PL_numeric_radix_sv && IN_LC(LC_NUMERIC)) {
+                radix_len  = SvCUR(PL_numeric_radix_sv);
+                /* note that this will convert the output to utf8 even if
+                 * if the radix point didn't get output */
+                is_utf8 = SvUTF8(PL_numeric_radix_sv);
+            }
+            RESTORE_LC_NUMERIC();
+#endif
+            /* most basic: space for "Inf"/"Nan" and \0, plus the "." */
+           float_need = 4 + radix_len;
+
            /* frexp() (or frexpl) has some unspecified behaviour for
              * nan/inf/-inf, so let's avoid calling that on non-finites. */
            if (isALPHA_FOLD_NE(c, 'e') && FV_ISFINITE(fv)) {
@@ -12460,12 +12480,6 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
                     float_need += (DOUBLEDOUBLE_MAXBITS/8 + 1) * 2;
                     /* the size for the exponent already added */
 #endif
-#ifdef USE_LOCALE_NUMERIC
-                        STORE_LC_NUMERIC_SET_TO_NEEDED();
-                        if (PL_numeric_radix_sv && IN_LC(LC_NUMERIC))
-                            float_need += SvCUR(PL_numeric_radix_sv);
-                        RESTORE_LC_NUMERIC();
-#endif
                 }
                 else if (i > 0) {
                     float_need = BIT_DIGITS(i);
@@ -12947,15 +12961,6 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
            eptr = PL_efloatbuf;
             assert((IV)elen > 0); /* here zero elen is bad */
 
-#ifdef USE_LOCALE_NUMERIC
-            /* If the decimal point character in the string is UTF-8, make the
-             * output utf8 */
-            if (PL_numeric_radix_sv && SvUTF8(PL_numeric_radix_sv)
-                && instr(eptr, SvPVX_const(PL_numeric_radix_sv)))
-            {
-                is_utf8 = TRUE;
-            }
-#endif
 
            break;