Bug 1398354 part 1. Expose StringIsArrayIndex taking a char pointer in jsfriendapi. r=waldo
authorBoris Zbarsky <bzbarsky@mit.edu>
Wed, 17 Oct 2018 23:00:49 -0400
changeset 490351 b7142afba71297a36d86eb651a795b33223c2fb6
parent 490350 9b412ade97d9c655a7599a5ea1cc95b9179c63a4
child 490352 59e0564b74358de53bb51500382b99fcca19a508
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewerswaldo
bugs1398354
milestone64.0a1
Bug 1398354 part 1. Expose StringIsArrayIndex taking a char pointer in jsfriendapi. r=waldo
js/src/builtin/Array.cpp
js/src/jsapi-tests/moz.build
js/src/jsapi-tests/testStringIsArrayIndex.cpp
js/src/jsfriendapi.h
--- a/js/src/builtin/Array.cpp
+++ b/js/src/builtin/Array.cpp
@@ -218,17 +218,17 @@ GetLengthProperty(JSContext* cx, HandleO
  * except that by using signed 31-bit integers we miss the top half of the
  * valid range. This function checks the string representation itself; note
  * that calling a standard conversion routine might allow strings such as
  * "08" or "4.0" as array indices, which they are not.
  *
  */
 template <typename CharT>
 static bool
-StringIsArrayIndex(const CharT* s, uint32_t length, uint32_t* indexp)
+StringIsArrayIndexHelper(const CharT* s, uint32_t length, uint32_t* indexp)
 {
     const CharT* end = s + length;
 
     if (length == 0 || length > (sizeof("4294967294") - 1) || !IsAsciiDigit(*s)) {
         return false;
     }
 
     uint32_t c = 0, previous = 0;
@@ -260,18 +260,30 @@ StringIsArrayIndex(const CharT* s, uint3
     return false;
 }
 
 JS_FRIEND_API(bool)
 js::StringIsArrayIndex(JSLinearString* str, uint32_t* indexp)
 {
     AutoCheckCannotGC nogc;
     return str->hasLatin1Chars()
-           ? ::StringIsArrayIndex(str->latin1Chars(nogc), str->length(), indexp)
-           : ::StringIsArrayIndex(str->twoByteChars(nogc), str->length(), indexp);
+           ? StringIsArrayIndexHelper(str->latin1Chars(nogc), str->length(), indexp)
+           : StringIsArrayIndexHelper(str->twoByteChars(nogc), str->length(), indexp);
+}
+
+JS_FRIEND_API(bool)
+js::StringIsArrayIndex(const char16_t* str, uint32_t length, uint32_t* indexp)
+{
+    return StringIsArrayIndexHelper(str, length, indexp);
+}
+
+JS_FRIEND_API(bool)
+js::StringIsArrayIndex(const char* str, uint32_t length, uint32_t* indexp)
+{
+    return StringIsArrayIndexHelper(str, length, indexp);
 }
 
 template <typename T>
 static bool
 ToId(JSContext* cx, T index, MutableHandleId id);
 
 template <>
 bool
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -92,16 +92,17 @@ UNIFIED_SOURCES += [
     'testSavedStacks.cpp',
     'testScriptInfo.cpp',
     'testScriptObject.cpp',
     'testSetProperty.cpp',
     'testSetPropertyIgnoringNamedGetter.cpp',
     'testSharedImmutableStringsCache.cpp',
     'testSourcePolicy.cpp',
     'testStringBuffer.cpp',
+    'testStringIsArrayIndex.cpp',
     'testStructuredClone.cpp',
     'testSymbol.cpp',
     'testThreadingConditionVariable.cpp',
     'testThreadingExclusiveData.cpp',
     'testThreadingMutex.cpp',
     'testThreadingThread.cpp',
     'testToSignedOrUnsignedInteger.cpp',
     'testTypedArrays.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testStringIsArrayIndex.cpp
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jsfriendapi.h"
+
+#include "jsapi-tests/tests.h"
+
+using mozilla::ArrayLength;
+
+// Need to account for string literals including the \0 at the end.
+#define STR(x) x, (ArrayLength(x)-1)
+
+static const struct TestTuple {
+    /* The string being tested. */
+    const char* string;
+    /* The number of characters from the string to use. */
+    size_t length;
+    /* Whether this string is an index. */
+    bool isindex;
+    /* If it's an index, what index it is.  Ignored if not an index. */
+    uint32_t index;
+} tests[] = {
+    { STR("0"), true, 0 },
+    { STR("1"), true, 1 },
+    { STR("2"), true, 2 },
+    { STR("9"), true, 9 },
+    { STR("10"), true, 10 },
+    { STR("15"), true, 15 },
+    { STR("16"), true, 16 },
+    { STR("17"), true, 17 },
+    { STR("99"), true, 99 },
+    { STR("100"), true, 100 },
+    { STR("255"), true, 255 },
+    { STR("256"), true, 256 },
+    { STR("257"), true, 257 },
+    { STR("999"), true, 999 },
+    { STR("1000"), true, 1000 },
+    { STR("4095"), true, 4095 },
+    { STR("4096"), true, 4096 },
+    { STR("9999"), true, 9999 },
+    { STR("1073741823"), true, 1073741823 },
+    { STR("1073741824"), true, 1073741824 },
+    { STR("1073741825"), true, 1073741825 },
+    { STR("2147483647"), true, 2147483647 },
+    { STR("2147483648"), true, 2147483648u },
+    { STR("2147483649"), true, 2147483649u },
+    { STR("4294967294"), true, 4294967294u },
+    { STR("4294967295"), false, 0 },  // Not an array index because need to be able to represent length
+    { STR("-1"), false, 0 },
+    { STR("abc"), false, 0 },
+    { STR(" 0"), false, 0 },
+    { STR("0 "), false, 0 },
+    // Tests to make sure the passed-in length is taken into account
+    { "0 ", 1, true, 0 },
+    { "123abc", 3, true, 123 },
+    { "123abc", 2, true, 12 },
+};
+
+BEGIN_TEST(testStringIsArrayIndex)
+{
+    for (size_t i = 0, sz = ArrayLength(tests); i < sz; i++) {
+        uint32_t index;
+        bool isindex = js::StringIsArrayIndex(tests[i].string,
+                                              tests[i].length,
+                                              &index);
+        CHECK_EQUAL(isindex, tests[i].isindex);
+        if (isindex) {
+            CHECK_EQUAL(index, tests[i].index);
+        }
+    }
+
+    return true;
+}
+END_TEST(testStringIsArrayIndex)
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1002,19 +1002,36 @@ CopyFlatStringChars(char16_t* dest, JSFl
  * results that match the output of Reflect.ownKeys.
  */
 JS_FRIEND_API(bool)
 GetPropertyKeys(JSContext* cx, JS::HandleObject obj, unsigned flags, JS::AutoIdVector* props);
 
 JS_FRIEND_API(bool)
 AppendUnique(JSContext* cx, JS::AutoIdVector& base, JS::AutoIdVector& others);
 
+/**
+ * Determine whether the given string is an array index in the sense of <https://tc39.github.io/ecma262/#array-index>.
+ *
+ * If it isn't, returns false.
+ *
+ * If it is, returns true and outputs the index in *indexp.
+ */
 JS_FRIEND_API(bool)
 StringIsArrayIndex(JSLinearString* str, uint32_t* indexp);
 
+/**
+ * Overloads of StringIsArrayIndex taking (char*,length) pairs.  These
+ * behave the same as the JSLinearString version.
+ */
+JS_FRIEND_API(bool)
+StringIsArrayIndex(const char* str, uint32_t length, uint32_t* indexp);
+
+JS_FRIEND_API(bool)
+StringIsArrayIndex(const char16_t* str, uint32_t length, uint32_t* indexp);
+
 JS_FRIEND_API(void)
 SetPreserveWrapperCallback(JSContext* cx, PreserveWrapperCallback callback);
 
 JS_FRIEND_API(bool)
 IsObjectInContextCompartment(JSObject* obj, const JSContext* cx);
 
 /*
  * NB: keep these in sync with the copy in builtin/SelfHostingDefines.h.