This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Implement grok_atou as safe/strict atoi replacement.
[perl5.git] / numeric.c
index b5144f4..d997d11 100644 (file)
--- a/numeric.c
+++ b/numeric.c
 /*
 =head1 Numeric functions
 
+=cut
+
 This file contains all the stuff needed by perl for manipulating numeric
 values, including such things as replacements for the OS's atof() function
 
-=cut
-
 */
 
 #include "EXTERN.h"
@@ -30,9 +30,8 @@ values, including such things as replacements for the OS's atof() function
 #include "perl.h"
 
 U32
-Perl_cast_ulong(pTHX_ NV f)
+Perl_cast_ulong(NV f)
 {
-    PERL_UNUSED_CONTEXT;
   if (f < 0.0)
     return f < I32_MIN ? (U32) I32_MIN : (U32)(I32) f;
   if (f < U32_MAX_P1) {
@@ -49,9 +48,8 @@ Perl_cast_ulong(pTHX_ NV f)
 }
 
 I32
-Perl_cast_i32(pTHX_ NV f)
+Perl_cast_i32(NV f)
 {
-    PERL_UNUSED_CONTEXT;
   if (f < I32_MAX_P1)
     return f < I32_MIN ? I32_MIN : (I32) f;
   if (f < U32_MAX_P1) {
@@ -68,9 +66,8 @@ Perl_cast_i32(pTHX_ NV f)
 }
 
 IV
-Perl_cast_iv(pTHX_ NV f)
+Perl_cast_iv(NV f)
 {
-    PERL_UNUSED_CONTEXT;
   if (f < IV_MAX_P1)
     return f < IV_MIN ? IV_MIN : (IV) f;
   if (f < UV_MAX_P1) {
@@ -88,9 +85,8 @@ Perl_cast_iv(pTHX_ NV f)
 }
 
 UV
-Perl_cast_uv(pTHX_ NV f)
+Perl_cast_uv(NV f)
 {
-    PERL_UNUSED_CONTEXT;
   if (f < 0.0)
     return f < IV_MIN ? (UV) IV_MIN : (UV)(IV) f;
   if (f < UV_MAX_P1) {
@@ -263,7 +259,6 @@ on this platform.
 UV
 Perl_grok_hex(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result)
 {
-    dVAR;
     const char *s = start;
     STRLEN len = *len_p;
     UV value = 0;
@@ -524,17 +519,20 @@ bool
 Perl_grok_numeric_radix(pTHX_ const char **sp, const char *send)
 {
 #ifdef USE_LOCALE_NUMERIC
-    dVAR;
-
     PERL_ARGS_ASSERT_GROK_NUMERIC_RADIX;
 
-    if (PL_numeric_radix_sv && IN_SOME_LOCALE_FORM) {
-        STRLEN len;
-        const char * const radix = SvPV(PL_numeric_radix_sv, len);
-        if (*sp + len <= send && memEQ(*sp, radix, len)) {
-            *sp += len;
-            return TRUE; 
+    if (IN_LC(LC_NUMERIC)) {
+        DECLARE_STORE_LC_NUMERIC_SET_TO_NEEDED();
+        if (PL_numeric_radix_sv) {
+            STRLEN len;
+            const char * const radix = SvPV(PL_numeric_radix_sv, len);
+            if (*sp + len <= send && memEQ(*sp, radix, len)) {
+                *sp += len;
+                RESTORE_LC_NUMERIC();
+                return TRUE;
+            }
         }
+        RESTORE_LC_NUMERIC();
     }
     /* always try "." if numeric radix didn't match because
      * we may have data from different locales mixed */
@@ -550,7 +548,7 @@ Perl_grok_numeric_radix(pTHX_ const char **sp, const char *send)
 }
 
 /*
-=for apidoc grok_number
+=for apidoc grok_number_flags
 
 Recognise (or not) a number.  The type of the number is returned
 (0 if unrecognised), otherwise it is a bit-ORed combination of
@@ -570,11 +568,27 @@ IS_NUMBER_NEG if the number is negative (in which case *valuep holds the
 absolute value).  IS_NUMBER_IN_UV is not set if e notation was used or the
 number is larger than a UV.
 
+C<flags> allows only C<PERL_SCAN_TRAILING>, which allows for trailing
+non-numeric text on an otherwise successful I<grok>, setting
+C<IS_NUMBER_TRAILING> on the result.
+
+=for apidoc grok_number
+
+Identical to grok_number_flags() with flags set to zero.
+
 =cut
  */
 int
 Perl_grok_number(pTHX_ const char *pv, STRLEN len, UV *valuep)
 {
+    PERL_ARGS_ASSERT_GROK_NUMBER;
+
+    return grok_number_flags(pv, len, valuep, 0);
+}
+
+int
+Perl_grok_number_flags(pTHX_ const char *pv, STRLEN len, UV *valuep, U32 flags)
+{
   const char *s = pv;
   const char * const send = pv + len;
   const UV max_div_10 = UV_MAX / 10;
@@ -583,7 +597,7 @@ Perl_grok_number(pTHX_ const char *pv, STRLEN len, UV *valuep)
   int sawinf = 0;
   int sawnan = 0;
 
-  PERL_ARGS_ASSERT_GROK_NUMBER;
+  PERL_ARGS_ASSERT_GROK_NUMBER_FLAGS;
 
   while (s < send && isSPACE(*s))
     s++;
@@ -738,9 +752,6 @@ Perl_grok_number(pTHX_ const char *pv, STRLEN len, UV *valuep)
   } else if (s < send) {
     /* we can have an optional exponent part */
     if (*s == 'e' || *s == 'E') {
-      /* The only flag we keep is sign.  Blow away any "it's UV"  */
-      numtype &= IS_NUMBER_NEG;
-      numtype |= IS_NUMBER_NOT_INT;
       s++;
       if (s < send && (*s == '-' || *s == '+'))
         s++;
@@ -749,8 +760,14 @@ Perl_grok_number(pTHX_ const char *pv, STRLEN len, UV *valuep)
           s++;
         } while (s < send && isDIGIT(*s));
       }
+      else if (flags & PERL_SCAN_TRAILING)
+        return numtype | IS_NUMBER_TRAILING;
       else
-      return 0;
+        return 0;
+
+      /* The only flag we keep is sign.  Blow away any "it's UV"  */
+      numtype &= IS_NUMBER_NEG;
+      numtype |= IS_NUMBER_NOT_INT;
     }
   }
   while (s < send && isSPACE(*s))
@@ -762,9 +779,87 @@ Perl_grok_number(pTHX_ const char *pv, STRLEN len, UV *valuep)
       *valuep = 0;
     return IS_NUMBER_IN_UV;
   }
+  else if (flags & PERL_SCAN_TRAILING) {
+    return numtype | IS_NUMBER_TRAILING;
+  }
+
   return 0;
 }
 
+/*
+=for perlapi
+
+grok_atou is a safer replacement for atoi.
+
+(atoi has severe problems with illegal inputs, and should not be used.
+atoi is also affected by locale settings, which can be seen as a bug.)
+
+Returns the unsigned value, if a valid one can be parsed.
+
+Only the decimal digits '0'..'9' are accepted.
+
+Does NOT allow optional leading whitespace, as opposed to atoi.
+
+On return the *endptr will contain the pointer to the first non-digit byte.
+
+If the value overflows, returns Size_t_MAX, and sets the *endptr
+to NULL, unless endptr is NULL.
+
+If the endptr is NULL, the first non-digit byte MUST be
+the zero byte terminating the pv, or either zero or Size_t_MAX
+will be returned, as appropriate.
+
+=cut
+*/
+
+Size_t
+Perl_grok_atou(const char *pv, const char** endptr)
+{
+    const char* s = pv;
+    const char** eptr;
+    const char* end2; /* Used in case endptr is NULL. */
+    Size_t val = 0; /* The return value. */
+
+    PERL_ARGS_ASSERT_GROK_ATOU;
+
+    eptr = endptr ? endptr : &end2;
+    if (isDIGIT(*s) && !isDIGIT(*(s + 1))) {
+        /* Quite common cases, and in addition the case of zero ("0")
+         * simplifies the decoding loop: not having to think whether
+         * "000" or "000123" are valid (now they are invalid). */
+        val = *s++ - '0';
+    } else {
+        Size_t tmp = 0; /* Temporary accumulator. */
+
+        while (*s) {
+            /* This could be unrolled like in grok_number(), but
+             * the expected uses of this are not speed-needy, and
+             * unlikely to need 64-bitness. */
+            if (isDIGIT(*s)) {
+                int digit = *s++ - '0';
+                tmp = tmp * 10 + digit;
+                if (tmp > val) { /* Rejects leading zeros. */
+                    val = tmp;
+                } else { /* Overflow. */
+                    *eptr = NULL;
+                    return Size_t_MAX;
+                }
+            } else {
+                break;
+            }
+        }
+        if (s == pv) {
+            *eptr = NULL; /* If no progress, failed to parse anything. */
+            return 0;
+        }
+    }
+    if (endptr == NULL && *s) {
+        return 0; /* If endptr is NULL, no trailing non-digits allowed. */
+    }
+    *eptr = s;
+    return val;
+}
+
 STATIC NV
 S_mulexp10(NV value, I32 exponent)
 {
@@ -849,35 +944,38 @@ Perl_my_atof(pTHX_ const char* s)
 {
     NV x = 0.0;
 #ifdef USE_LOCALE_NUMERIC
-    dVAR;
-
     PERL_ARGS_ASSERT_MY_ATOF;
 
-    if (PL_numeric_local && PL_numeric_radix_sv && IN_SOME_LOCALE_FORM) {
-        const char *standard = NULL, *local = NULL;
-        bool use_standard_radix;
+    {
+        DECLARE_STORE_LC_NUMERIC_SET_TO_NEEDED();
+        if (PL_numeric_radix_sv && IN_LC(LC_NUMERIC)) {
+            const char *standard = NULL, *local = NULL;
+            bool use_standard_radix;
 
-        /* Look through the string for the first thing that looks like a
-         * decimal point: either the value in the current locale or the
-         * standard fallback of '.'. The one which appears earliest in the
-         * input string is the one that we should have atof look for. Note that
-         * we have to determine this beforehand because on some systems,
-         * Perl_atof2 is just a wrapper around the system's atof. */
-        standard = strchr(s, '.');
-        local = strstr(s, SvPV_nolen(PL_numeric_radix_sv));
+            /* Look through the string for the first thing that looks like a
+             * decimal point: either the value in the current locale or the
+             * standard fallback of '.'. The one which appears earliest in the
+             * input string is the one that we should have atof look for. Note
+             * that we have to determine this beforehand because on some
+             * systems, Perl_atof2 is just a wrapper around the system's atof.
+             * */
+            standard = strchr(s, '.');
+            local = strstr(s, SvPV_nolen(PL_numeric_radix_sv));
 
-        use_standard_radix = standard && (!local || standard < local);
+            use_standard_radix = standard && (!local || standard < local);
 
-        if (use_standard_radix)
-            SET_NUMERIC_STANDARD();
+            if (use_standard_radix)
+                SET_NUMERIC_STANDARD();
 
-        Perl_atof2(s, x);
+            Perl_atof2(s, x);
 
-        if (use_standard_radix)
-            SET_NUMERIC_LOCAL();
+            if (use_standard_radix)
+                SET_NUMERIC_LOCAL();
+        }
+        else
+            Perl_atof2(s, x);
+        RESTORE_LC_NUMERIC();
     }
-    else
-       Perl_atof2(s, x);
 #else
     Perl_atof2(s, x);
 #endif
@@ -942,7 +1040,7 @@ Perl_my_atof2(pTHX_ const char* orig, NV* value)
     switch (*s) {
        case '-':
            negative = 1;
-           /* fall through */
+           /* FALLTHROUGH */
        case '+':
            ++s;
     }
@@ -1040,7 +1138,7 @@ Perl_my_atof2(pTHX_ const char* orig, NV* value)
        switch (*s) {
            case '-':
                expnegative = 1;
-               /* fall through */
+               /* FALLTHROUGH */
            case '+':
                ++s;
        }