This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Introduce maxend for paranoia. Also, comments.
[perl5.git] / numeric.c
index e0ffafa..7efd76e 100644 (file)
--- a/numeric.c
+++ b/numeric.c
@@ -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,8 +519,6 @@ bool
 Perl_grok_numeric_radix(pTHX_ const char **sp, const char *send)
 {
 #ifdef USE_LOCALE_NUMERIC
-    dVAR;
-
     PERL_ARGS_ASSERT_GROK_NUMERIC_RADIX;
 
     if (IN_LC(LC_NUMERIC)) {
@@ -555,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
@@ -575,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;
@@ -588,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++;
@@ -743,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++;
@@ -754,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))
@@ -767,9 +779,102 @@ 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 and strtoul.
+
+atoi has severe problems with illegal inputs, cannot be used
+for incremental parsing, and therefore should be avoided.
+
+atoi and strtoul are also affected by locale settings, which can
+also be seen as a bug (global state controlled by user environment).
+
+grok_atou parses a C-style zero-byte terminated string.
+
+Returns the unsigned value, if a valid one can be parsed.
+
+Only the decimal digits '0'..'9' are accepted.
+
+As opposed to atoi or strtoul:
+- does NOT allow optional leading whitespace
+- does NOT allow negative inputs
+
+Also rejected are:
+- leading plus signs
+- leading zeros (meaning that only "0" is the zero)
+
+Trailing non-digit bytes are allowed if the endptr is non-NULL.
+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 zero will be returned.
+
+=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. */
+    /* With Size_t_size of 8 or 4 this works out to be the start plus
+     * either 20 or 10.  When 128 or 256-bit systems became reality,
+     * this overshoots (should get 39, 78, but gets 40 and 80). */
+    const char* maxend = s + 10 * (Size_t_size / 4);
+    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 < maxend && *s) {
+            /* This could be unrolled like in grok_number(), but
+             * the expected uses of this are not speed-needy, and
+             * unlikely to need full 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)
 {
@@ -854,8 +959,6 @@ Perl_my_atof(pTHX_ const char* s)
 {
     NV x = 0.0;
 #ifdef USE_LOCALE_NUMERIC
-    dVAR;
-
     PERL_ARGS_ASSERT_MY_ATOF;
 
     {