Bug 1020420 part 1 - Make parseInt work with Latin1 strings. r=njn
authorJan de Mooij <jdemooij@mozilla.com>
Fri, 06 Jun 2014 11:17:47 +0200
changeset 207379 32b157ae5ed7752907b7158eaccd96499a14c789
parent 207378 74f5be69e4c9df83f25cd1028513da267becc26e
child 207380 1962fd3aa8193c1a4678961833a1b15a4c241258
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnjn
bugs1020420
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1020420 part 1 - Make parseInt work with Latin1 strings. r=njn
js/src/jit-test/tests/latin1/parseInt-parseFloat.js
js/src/jsnum.cpp
js/src/jsnum.h
js/src/jsstr.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/latin1/parseInt-parseFloat.js
@@ -0,0 +1,20 @@
+function testParseInt() {
+    // Latin1
+    assertEq(parseInt(toLatin1("12345abc")), 12345);
+    assertEq(parseInt(toLatin1("0x5")), 0x5);
+    assertEq(parseInt(toLatin1("-123")), -123);
+    assertEq(parseInt(toLatin1("xyz")), NaN);
+    assertEq(parseInt(toLatin1("1234GHI"), 17), 94298);
+    assertEq(parseInt(toLatin1("9007199254749999")), 9007199254750000);
+    assertEq(parseInt(toLatin1("9007199254749998"), 16), 10378291982571444000);
+
+    // TwoByte
+    assertEq(parseInt("12345abc\u1200"), 12345);
+    assertEq(parseInt("0x5\u1200"), 0x5);
+    assertEq(parseInt("  -123\u1200"), -123);
+    assertEq(parseInt("\u1200"), NaN);
+    assertEq(parseInt("1234GHI\u1200", 17), 94298);
+    assertEq(parseInt("9007199254749999\u1200"), 9007199254750000);
+    assertEq(parseInt("9007199254749998\u1200", 16), 10378291982571444000);
+}
+testParseInt();
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -48,56 +48,57 @@ using mozilla::PositiveInfinity;
 using mozilla::RangedPtr;
 using JS::GenericNaN;
 
 /*
  * If we're accumulating a decimal number and the number is >= 2^53, then the
  * fast result from the loop in Get{Prefix,Decimal}Integer may be inaccurate.
  * Call js_strtod_harder to get the correct answer.
  */
+template <typename CharT>
 static bool
-ComputeAccurateDecimalInteger(ThreadSafeContext *cx,
-                              const jschar *start, const jschar *end, double *dp)
+ComputeAccurateDecimalInteger(ThreadSafeContext *cx, const CharT *start, const CharT *end,
+                              double *dp)
 {
     size_t length = end - start;
-    char *cstr = cx->pod_malloc<char>(length + 1);
+    ScopedJSFreePtr<char> cstr(cx->pod_malloc<char>(length + 1));
     if (!cstr)
         return false;
 
     for (size_t i = 0; i < length; i++) {
         char c = char(start[i]);
         JS_ASSERT(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'));
         cstr[i] = c;
     }
     cstr[length] = 0;
 
     char *estr;
     int err = 0;
     *dp = js_strtod_harder(cx->dtoaState(), cstr, &estr, &err);
     if (err == JS_DTOA_ENOMEM) {
         js_ReportOutOfMemory(cx);
-        js_free(cstr);
         return false;
     }
-    js_free(cstr);
+
     return true;
 }
 
 namespace {
 
+template <typename CharT>
 class BinaryDigitReader
 {
     const int base;      /* Base of number; must be a power of 2 */
     int digit;           /* Current digit value in radix given by base */
     int digitMask;       /* Mask to extract the next bit from digit */
-    const jschar *start; /* Pointer to the remaining digits */
-    const jschar *end;   /* Pointer to first non-digit */
+    const CharT *start;  /* Pointer to the remaining digits */
+    const CharT *end;    /* Pointer to first non-digit */
 
   public:
-    BinaryDigitReader(int base, const jschar *start, const jschar *end)
+    BinaryDigitReader(int base, const CharT *start, const CharT *end)
       : base(base), digit(0), digitMask(0), start(start), end(end)
     {
     }
 
     /* Return the next binary digit from the number, or -1 if done. */
     int nextDigit() {
         if (digitMask == 0) {
             if (start == end)
@@ -126,20 +127,21 @@ class BinaryDigitReader
  * The fast result might also have been inaccurate for power-of-two bases. This
  * happens if the addition in value * 2 + digit causes a round-down to an even
  * least significant mantissa bit when the first dropped bit is a one.  If any
  * of the following digits in the number (which haven't been added in yet) are
  * nonzero, then the correct action would have been to round up instead of
  * down.  An example occurs when reading the number 0x1000000000000081, which
  * rounds to 0x1000000000000000 instead of 0x1000000000000100.
  */
+template <typename CharT>
 static double
-ComputeAccurateBinaryBaseInteger(const jschar *start, const jschar *end, int base)
+ComputeAccurateBinaryBaseInteger(const CharT *start, const CharT *end, int base)
 {
-    BinaryDigitReader bdr(base, start, end);
+    BinaryDigitReader<CharT> bdr(base, start, end);
 
     /* Skip leading zeroes. */
     int bit;
     do {
         bit = bdr.nextDigit();
     } while (bit == 0);
 
     JS_ASSERT(bit == 1); // guaranteed by Get{Prefix,Decimal}Integer
@@ -184,28 +186,29 @@ js::ParseDecimalNumber(const JS::TwoByte
         uint64_t next = dec * 10 + digit;
         MOZ_ASSERT(next < DOUBLE_INTEGRAL_PRECISION_LIMIT,
                    "next value won't be an integrally-precise double");
         dec = next;
     } while (++s < end);
     return static_cast<double>(dec);
 }
 
+template <typename CharT>
 bool
-js::GetPrefixInteger(ThreadSafeContext *cx, const jschar *start, const jschar *end, int base,
-                     const jschar **endp, double *dp)
+js::GetPrefixInteger(ThreadSafeContext *cx, const CharT *start, const CharT *end, int base,
+                     const CharT **endp, double *dp)
 {
     JS_ASSERT(start <= end);
     JS_ASSERT(2 <= base && base <= 36);
 
-    const jschar *s = start;
+    const CharT *s = start;
     double d = 0.0;
     for (; s < end; s++) {
         int digit;
-        jschar c = *s;
+        CharT c = *s;
         if ('0' <= c && c <= '9')
             digit = c - '0';
         else if ('a' <= c && c <= 'z')
             digit = c - 'a' + 10;
         else if ('A' <= c && c <= 'Z')
             digit = c - 'A' + 10;
         else
             break;
@@ -223,16 +226,17 @@ js::GetPrefixInteger(ThreadSafeContext *
 
     /*
      * Otherwise compute the correct integer from the prefix of valid digits
      * if we're computing for base ten or a power of two.  Don't worry about
      * other bases; see 15.1.2.2 step 13.
      */
     if (base == 10)
         return ComputeAccurateDecimalInteger(cx, start, s, dp);
+
     if ((base & (base - 1)) == 0)
         *dp = ComputeAccurateBinaryBaseInteger(start, s, base);
 
     return true;
 }
 
 bool
 js::GetDecimalInteger(ExclusiveContext *cx, const jschar *start, const jschar *end, double *dp)
@@ -317,16 +321,56 @@ num_parseFloat(JSContext *cx, unsigned a
     if (ep == bp) {
         args.rval().setNaN();
         return true;
     }
     args.rval().setDouble(d);
     return true;
 }
 
+template <typename CharT>
+static bool
+ParseIntImpl(JSContext *cx, const CharT *chars, size_t length, bool stripPrefix, int32_t radix,
+             double *res)
+{
+    /* Step 2. */
+    const CharT *end = chars + length;
+    const CharT *s = SkipSpace(chars, end);
+
+    MOZ_ASSERT(chars <= s);
+    MOZ_ASSERT(s <= end);
+
+    /* Steps 3-4. */
+    bool negative = (s != end && s[0] == '-');
+
+    /* Step 5. */
+    if (s != end && (s[0] == '-' || s[0] == '+'))
+        s++;
+
+    /* Step 10. */
+    if (stripPrefix) {
+        if (end - s >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
+            s += 2;
+            radix = 16;
+        }
+    }
+
+    /* Steps 11-15. */
+    const CharT *actualEnd;
+    double d;
+    if (!GetPrefixInteger(cx, s, end, radix, &actualEnd, &d))
+        return false;
+
+    if (s == actualEnd)
+        *res = GenericNaN();
+    else
+        *res = negative ? -d : d;
+    return true;
+}
+
 /* ES5 15.1.2.2. */
 bool
 js::num_parseInt(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     /* Fast paths and exceptional cases. */
     if (args.length() == 0) {
@@ -390,54 +434,32 @@ js::num_parseInt(JSContext *cx, unsigned
                 args.rval().setNaN();
                 return true;
             }
             if (radix != 16)
                 stripPrefix = false;
         }
     }
 
-    /* Step 2. */
-    const jschar *s;
-    const jschar *end;
-    {
-        const jschar *ws = inputString->getChars(cx);
-        if (!ws)
+    JSLinearString *linear = inputString->ensureLinear(cx);
+    if (!linear)
+        return false;
+
+    AutoCheckCannotGC nogc;
+    size_t length = inputString->length();
+    double number;
+    if (linear->hasLatin1Chars()) {
+        if (!ParseIntImpl(cx, linear->latin1Chars(nogc), length, stripPrefix, radix, &number))
             return false;
-        end = ws + inputString->length();
-        s = SkipSpace(ws, end);
-
-        MOZ_ASSERT(ws <= s);
-        MOZ_ASSERT(s <= end);
+    } else {
+        if (!ParseIntImpl(cx, linear->twoByteChars(nogc), length, stripPrefix, radix, &number))
+            return false;
     }
 
-    /* Steps 3-4. */
-    bool negative = (s != end && s[0] == '-');
-
-    /* Step 5. */
-    if (s != end && (s[0] == '-' || s[0] == '+'))
-        s++;
-
-    /* Step 10. */
-    if (stripPrefix) {
-        if (end - s >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
-            s += 2;
-            radix = 16;
-        }
-    }
-
-    /* Steps 11-15. */
-    const jschar *actualEnd;
-    double number;
-    if (!GetPrefixInteger(cx, s, end, radix, &actualEnd, &number))
-        return false;
-    if (s == actualEnd)
-        args.rval().setNaN();
-    else
-        args.rval().setNumber(negative ? -number : number);
+    args.rval().setNumber(number);
     return true;
 }
 
 static const JSFunctionSpec number_functions[] = {
     JS_FN(js_isNaN_str,         num_isNaN,           1,0),
     JS_FN(js_isFinite_str,      num_isFinite,        1,0),
     JS_FN(js_parseFloat_str,    num_parseFloat,      1,0),
     JS_FN(js_parseInt_str,      num_parseInt,        2,0),
--- a/js/src/jsnum.h
+++ b/js/src/jsnum.h
@@ -127,19 +127,20 @@ ParseDecimalNumber(const JS::TwoByteChar
  * reading the digits of the integer.  Return the index one past the end of the
  * digits of the integer in *endp, and return the integer itself in *dp.  If
  * base is 10 or a power of two the returned integer is the closest possible
  * double; otherwise extremely large integers may be slightly inaccurate.
  *
  * If [start, end) does not begin with a number with the specified base,
  * *dp == 0 and *endp == start upon return.
  */
+template <typename CharT>
 extern bool
-GetPrefixInteger(ThreadSafeContext *cx, const jschar *start, const jschar *end, int base,
-                 const jschar **endp, double *dp);
+GetPrefixInteger(ThreadSafeContext *cx, const CharT *start, const CharT *end, int base,
+                 const CharT **endp, double *dp);
 
 /*
  * This is like GetPrefixInteger, but only deals with base 10, and doesn't have
  * and |endp| outparam.  It should only be used when the jschars are known to
  * only contain digits.
  */
 extern bool
 GetDecimalInteger(ExclusiveContext *cx, const jschar *start, const jschar *end, double *dp);
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -29,18 +29,19 @@ class MutatingRopeSegmentRange;
 
 template <AllowGC allowGC>
 extern JSString *
 ConcatStrings(ThreadSafeContext *cx,
               typename MaybeRooted<JSString*, allowGC>::HandleType left,
               typename MaybeRooted<JSString*, allowGC>::HandleType right);
 
 // Return s advanced past any Unicode white space characters.
-static inline const jschar *
-SkipSpace(const jschar *s, const jschar *end)
+template <typename CharT>
+static inline const CharT *
+SkipSpace(const CharT *s, const CharT *end)
 {
     JS_ASSERT(s <= end);
 
     while (s < end && unicode::IsSpace(*s))
         s++;
 
     return s;
 }