summaryrefslogtreecommitdiffstats
path: root/js/src/builtin
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin')
-rw-r--r--js/src/builtin/Array.js1149
-rw-r--r--js/src/builtin/AtomicsObject.cpp1152
-rw-r--r--js/src/builtin/AtomicsObject.h156
-rw-r--r--js/src/builtin/Classes.js17
-rw-r--r--js/src/builtin/Date.js174
-rw-r--r--js/src/builtin/Error.js36
-rw-r--r--js/src/builtin/Eval.cpp485
-rw-r--r--js/src/builtin/Eval.h42
-rw-r--r--js/src/builtin/Function.js284
-rw-r--r--js/src/builtin/Generator.js148
-rw-r--r--js/src/builtin/Intl.cpp2990
-rw-r--r--js/src/builtin/Intl.h409
-rw-r--r--js/src/builtin/Intl.js3008
-rw-r--r--js/src/builtin/IntlData.js382
-rw-r--r--js/src/builtin/IntlTimeZoneData.h137
-rw-r--r--js/src/builtin/Iterator.js127
-rw-r--r--js/src/builtin/Map.js138
-rw-r--r--js/src/builtin/MapObject.cpp1751
-rw-r--r--js/src/builtin/MapObject.h341
-rw-r--r--js/src/builtin/Module.js328
-rw-r--r--js/src/builtin/ModuleObject.cpp1331
-rw-r--r--js/src/builtin/ModuleObject.h358
-rw-r--r--js/src/builtin/Number.js87
-rw-r--r--js/src/builtin/Object.cpp1341
-rw-r--r--js/src/builtin/Object.h87
-rw-r--r--js/src/builtin/Object.js198
-rw-r--r--js/src/builtin/Profilers.cpp576
-rw-r--r--js/src/builtin/Profilers.h95
-rw-r--r--js/src/builtin/Promise.cpp2794
-rw-r--r--js/src/builtin/Promise.h191
-rw-r--r--js/src/builtin/Promise.js16
-rw-r--r--js/src/builtin/Reflect.cpp288
-rw-r--r--js/src/builtin/Reflect.h29
-rw-r--r--js/src/builtin/Reflect.js112
-rw-r--r--js/src/builtin/ReflectParse.cpp3752
-rw-r--r--js/src/builtin/RegExp.cpp1781
-rw-r--r--js/src/builtin/RegExp.h159
-rw-r--r--js/src/builtin/RegExp.js1032
-rw-r--r--js/src/builtin/RegExpGlobalReplaceOpt.h.js128
-rw-r--r--js/src/builtin/RegExpLocalReplaceOpt.h.js92
-rw-r--r--js/src/builtin/SIMD.cpp1552
-rw-r--r--js/src/builtin/SIMD.h1219
-rw-r--r--js/src/builtin/SelfHostingDefines.h103
-rw-r--r--js/src/builtin/Set.js130
-rw-r--r--js/src/builtin/Sorting.js372
-rw-r--r--js/src/builtin/String.js872
-rw-r--r--js/src/builtin/SymbolObject.cpp234
-rw-r--r--js/src/builtin/SymbolObject.h65
-rw-r--r--js/src/builtin/TestingFunctions.cpp4814
-rw-r--r--js/src/builtin/TestingFunctions.h25
-rw-r--r--js/src/builtin/TypedArray.js1735
-rw-r--r--js/src/builtin/TypedObject.cpp3008
-rw-r--r--js/src/builtin/TypedObject.h1072
-rw-r--r--js/src/builtin/TypedObject.js1417
-rw-r--r--js/src/builtin/TypedObjectConstants.h121
-rw-r--r--js/src/builtin/Utilities.js227
-rw-r--r--js/src/builtin/WeakMap.js49
-rw-r--r--js/src/builtin/WeakMapObject.cpp390
-rw-r--r--js/src/builtin/WeakMapObject.h25
-rw-r--r--js/src/builtin/WeakSet.js108
-rw-r--r--js/src/builtin/WeakSetObject.cpp170
-rw-r--r--js/src/builtin/WeakSetObject.h37
-rw-r--r--js/src/builtin/embedjs.py159
-rwxr-xr-xjs/src/builtin/make_intl_data.py992
64 files changed, 46597 insertions, 0 deletions
diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js
new file mode 100644
index 000000000..54b47b72f
--- /dev/null
+++ b/js/src/builtin/Array.js
@@ -0,0 +1,1149 @@
+/* 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/. */
+
+ /* ES5 15.4.4.14. */
+function ArrayIndexOf(searchElement/*, fromIndex*/) {
+ /* Step 1. */
+ var O = ToObject(this);
+
+ /* Steps 2-3. */
+ var len = ToLength(O.length);
+
+ /* Step 4. */
+ if (len === 0)
+ return -1;
+
+ /* Step 5. Add zero to convert -0 to +0, per ES6 5.2. */
+ var n = arguments.length > 1 ? ToInteger(arguments[1]) + 0 : 0;
+
+ /* Step 6. */
+ if (n >= len)
+ return -1;
+
+ var k;
+ /* Step 7. */
+ if (n >= 0)
+ k = n;
+ /* Step 8. */
+ else {
+ /* Step a. */
+ k = len + n;
+ /* Step b. */
+ if (k < 0)
+ k = 0;
+ }
+
+ /* Step 9. */
+ if (IsPackedArray(O)) {
+ for (; k < len; k++) {
+ if (O[k] === searchElement)
+ return k;
+ }
+ } else {
+ for (; k < len; k++) {
+ if (k in O && O[k] === searchElement)
+ return k;
+ }
+ }
+
+ /* Step 10. */
+ return -1;
+}
+
+function ArrayStaticIndexOf(list, searchElement/*, fromIndex*/) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.indexOf');
+ var fromIndex = arguments.length > 2 ? arguments[2] : 0;
+ return callFunction(ArrayIndexOf, list, searchElement, fromIndex);
+}
+
+/* ES5 15.4.4.15. */
+function ArrayLastIndexOf(searchElement/*, fromIndex*/) {
+ /* Step 1. */
+ var O = ToObject(this);
+
+ /* Steps 2-3. */
+ var len = ToLength(O.length);
+
+ /* Step 4. */
+ if (len === 0)
+ return -1;
+
+ /* Step 5. Add zero to convert -0 to +0, per ES6 5.2. */
+ var n = arguments.length > 1 ? ToInteger(arguments[1]) + 0 : len - 1;
+
+ /* Steps 6-7. */
+ var k;
+ if (n > len - 1)
+ k = len - 1;
+ else if (n < 0)
+ k = len + n;
+ else
+ k = n;
+
+ /* Step 8. */
+ if (IsPackedArray(O)) {
+ for (; k >= 0; k--) {
+ if (O[k] === searchElement)
+ return k;
+ }
+ } else {
+ for (; k >= 0; k--) {
+ if (k in O && O[k] === searchElement)
+ return k;
+ }
+ }
+
+ /* Step 9. */
+ return -1;
+}
+
+function ArrayStaticLastIndexOf(list, searchElement/*, fromIndex*/) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.lastIndexOf');
+ var fromIndex;
+ if (arguments.length > 2) {
+ fromIndex = arguments[2];
+ } else {
+ var O = ToObject(list);
+ var len = ToLength(O.length);
+ fromIndex = len - 1;
+ }
+ return callFunction(ArrayLastIndexOf, list, searchElement, fromIndex);
+}
+
+/* ES5 15.4.4.16. */
+function ArrayEvery(callbackfn/*, thisArg*/) {
+ /* Step 1. */
+ var O = ToObject(this);
+
+ /* Steps 2-3. */
+ var len = ToLength(O.length);
+
+ /* Step 4. */
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.prototype.every');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ /* Step 5. */
+ var T = arguments.length > 1 ? arguments[1] : void 0;
+
+ /* Steps 6-7. */
+ /* Steps a (implicit), and d. */
+ for (var k = 0; k < len; k++) {
+ /* Step b */
+ if (k in O) {
+ /* Step c. */
+ if (!callContentFunction(callbackfn, T, O[k], k, O))
+ return false;
+ }
+ }
+
+ /* Step 8. */
+ return true;
+}
+
+function ArrayStaticEvery(list, callbackfn/*, thisArg*/) {
+ if (arguments.length < 2)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.every');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, callbackfn));
+ var T = arguments.length > 2 ? arguments[2] : void 0;
+ return callFunction(ArrayEvery, list, callbackfn, T);
+}
+
+/* ES5 15.4.4.17. */
+function ArraySome(callbackfn/*, thisArg*/) {
+ /* Step 1. */
+ var O = ToObject(this);
+
+ /* Steps 2-3. */
+ var len = ToLength(O.length);
+
+ /* Step 4. */
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.prototype.some');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ /* Step 5. */
+ var T = arguments.length > 1 ? arguments[1] : void 0;
+
+ /* Steps 6-7. */
+ /* Steps a (implicit), and d. */
+ for (var k = 0; k < len; k++) {
+ /* Step b */
+ if (k in O) {
+ /* Step c. */
+ if (callContentFunction(callbackfn, T, O[k], k, O))
+ return true;
+ }
+ }
+
+ /* Step 8. */
+ return false;
+}
+
+function ArrayStaticSome(list, callbackfn/*, thisArg*/) {
+ if (arguments.length < 2)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.some');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, callbackfn));
+ var T = arguments.length > 2 ? arguments[2] : void 0;
+ return callFunction(ArraySome, list, callbackfn, T);
+}
+
+/* ES6 draft 2016-1-15 22.1.3.25 Array.prototype.sort (comparefn) */
+function ArraySort(comparefn) {
+ /* Step 1. */
+ var O = ToObject(this);
+
+ /* Step 2. */
+ var len = ToLength(O.length);
+
+ if (len <= 1)
+ return this;
+
+ /* 22.1.3.25.1 Runtime Semantics: SortCompare( x, y ) */
+ var wrappedCompareFn = comparefn;
+ comparefn = function(x, y) {
+ /* Steps 1-3. */
+ if (x === undefined) {
+ if (y === undefined)
+ return 0;
+ return 1;
+ }
+ if (y === undefined)
+ return -1;
+
+ /* Step 4.a. */
+ var v = ToNumber(wrappedCompareFn(x, y));
+
+ /* Step 4.b-c. */
+ return v !== v ? 0 : v;
+ }
+
+ return MergeSort(O, len, comparefn);
+}
+
+/* ES5 15.4.4.18. */
+function ArrayForEach(callbackfn/*, thisArg*/) {
+ /* Step 1. */
+ var O = ToObject(this);
+
+ /* Steps 2-3. */
+ var len = ToLength(O.length);
+
+ /* Step 4. */
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.prototype.forEach');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ /* Step 5. */
+ var T = arguments.length > 1 ? arguments[1] : void 0;
+
+ /* Steps 6-7. */
+ /* Steps a (implicit), and d. */
+ for (var k = 0; k < len; k++) {
+ /* Step b */
+ if (k in O) {
+ /* Step c. */
+ callContentFunction(callbackfn, T, O[k], k, O);
+ }
+ }
+
+ /* Step 8. */
+ return void 0;
+}
+
+function ArrayStaticForEach(list, callbackfn/*, thisArg*/) {
+ if (arguments.length < 2)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.forEach');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, callbackfn));
+ var T = arguments.length > 2 ? arguments[2] : void 0;
+ callFunction(ArrayForEach, list, callbackfn, T);
+}
+
+/* ES 2016 draft Mar 25, 2016 22.1.3.15. */
+function ArrayMap(callbackfn/*, thisArg*/) {
+ /* Step 1. */
+ var O = ToObject(this);
+
+ /* Step 2. */
+ var len = ToLength(O.length);
+
+ /* Step 3. */
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.prototype.map');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ /* Step 4. */
+ var T = arguments.length > 1 ? arguments[1] : void 0;
+
+ /* Steps 5. */
+ var A = ArraySpeciesCreate(O, len);
+
+ /* Steps 6-7. */
+ /* Steps 7.a (implicit), and 7.d. */
+ for (var k = 0; k < len; k++) {
+ /* Steps 7.b-c. */
+ if (k in O) {
+ /* Steps 7.c.i-iii. */
+ var mappedValue = callContentFunction(callbackfn, T, O[k], k, O);
+ _DefineDataProperty(A, k, mappedValue);
+ }
+ }
+
+ /* Step 8. */
+ return A;
+}
+
+function ArrayStaticMap(list, callbackfn/*, thisArg*/) {
+ if (arguments.length < 2)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.map');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, callbackfn));
+ var T = arguments.length > 2 ? arguments[2] : void 0;
+ return callFunction(ArrayMap, list, callbackfn, T);
+}
+
+/* ES 2016 draft Mar 25, 2016 22.1.3.7 Array.prototype.filter. */
+function ArrayFilter(callbackfn/*, thisArg*/) {
+ /* Step 1. */
+ var O = ToObject(this);
+
+ /* Step 2. */
+ var len = ToLength(O.length);
+
+ /* Step 3. */
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.prototype.filter');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ /* Step 4. */
+ var T = arguments.length > 1 ? arguments[1] : void 0;
+
+ /* Step 5. */
+ var A = ArraySpeciesCreate(O, 0);
+
+ /* Steps 6-8. */
+ /* Steps 8.a (implicit), and 8.d. */
+ for (var k = 0, to = 0; k < len; k++) {
+ /* Steps 8.b-c. */
+ if (k in O) {
+ /* Step 8.c.i. */
+ var kValue = O[k];
+ /* Step 8.c.ii. */
+ var selected = callContentFunction(callbackfn, T, kValue, k, O);
+ /* Step 8.c.iii. */
+ if (selected)
+ _DefineDataProperty(A, to++, kValue);
+ }
+ }
+
+ /* Step 9. */
+ return A;
+}
+
+function ArrayStaticFilter(list, callbackfn/*, thisArg*/) {
+ if (arguments.length < 2)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.filter');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, callbackfn));
+ var T = arguments.length > 2 ? arguments[2] : void 0;
+ return callFunction(ArrayFilter, list, callbackfn, T);
+}
+
+/* ES5 15.4.4.21. */
+function ArrayReduce(callbackfn/*, initialValue*/) {
+ /* Step 1. */
+ var O = ToObject(this);
+
+ /* Steps 2-3. */
+ var len = ToLength(O.length);
+
+ /* Step 4. */
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.prototype.reduce');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ /* Step 6. */
+ var k = 0;
+
+ /* Steps 5, 7-8. */
+ var accumulator;
+ if (arguments.length > 1) {
+ accumulator = arguments[1];
+ } else {
+ /* Step 5. */
+ if (len === 0)
+ ThrowTypeError(JSMSG_EMPTY_ARRAY_REDUCE);
+ if (IsPackedArray(O)) {
+ accumulator = O[k++];
+ } else {
+ var kPresent = false;
+ for (; k < len; k++) {
+ if (k in O) {
+ accumulator = O[k];
+ kPresent = true;
+ k++;
+ break;
+ }
+ }
+ if (!kPresent)
+ ThrowTypeError(JSMSG_EMPTY_ARRAY_REDUCE);
+ }
+ }
+
+ /* Step 9. */
+ /* Steps a (implicit), and d. */
+ for (; k < len; k++) {
+ /* Step b */
+ if (k in O) {
+ /* Step c. */
+ accumulator = callbackfn(accumulator, O[k], k, O);
+ }
+ }
+
+ /* Step 10. */
+ return accumulator;
+}
+
+function ArrayStaticReduce(list, callbackfn) {
+ if (arguments.length < 2)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.reduce');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, callbackfn));
+ if (arguments.length > 2)
+ return callFunction(ArrayReduce, list, callbackfn, arguments[2]);
+ else
+ return callFunction(ArrayReduce, list, callbackfn);
+}
+
+/* ES5 15.4.4.22. */
+function ArrayReduceRight(callbackfn/*, initialValue*/) {
+ /* Step 1. */
+ var O = ToObject(this);
+
+ /* Steps 2-3. */
+ var len = ToLength(O.length);
+
+ /* Step 4. */
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.prototype.reduce');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ /* Step 6. */
+ var k = len - 1;
+
+ /* Steps 5, 7-8. */
+ var accumulator;
+ if (arguments.length > 1) {
+ accumulator = arguments[1];
+ } else {
+ /* Step 5. */
+ if (len === 0)
+ ThrowTypeError(JSMSG_EMPTY_ARRAY_REDUCE);
+ if (IsPackedArray(O)) {
+ accumulator = O[k--];
+ } else {
+ var kPresent = false;
+ for (; k >= 0; k--) {
+ if (k in O) {
+ accumulator = O[k];
+ kPresent = true;
+ k--;
+ break;
+ }
+ }
+ if (!kPresent)
+ ThrowTypeError(JSMSG_EMPTY_ARRAY_REDUCE);
+ }
+ }
+
+ /* Step 9. */
+ /* Steps a (implicit), and d. */
+ for (; k >= 0; k--) {
+ /* Step b */
+ if (k in O) {
+ /* Step c. */
+ accumulator = callbackfn(accumulator, O[k], k, O);
+ }
+ }
+
+ /* Step 10. */
+ return accumulator;
+}
+
+function ArrayStaticReduceRight(list, callbackfn) {
+ if (arguments.length < 2)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.reduceRight');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, callbackfn));
+ if (arguments.length > 2)
+ return callFunction(ArrayReduceRight, list, callbackfn, arguments[2]);
+ else
+ return callFunction(ArrayReduceRight, list, callbackfn);
+}
+
+/* ES6 draft 2013-05-14 15.4.3.23. */
+function ArrayFind(predicate/*, thisArg*/) {
+ /* Steps 1-2. */
+ var O = ToObject(this);
+
+ /* Steps 3-5. */
+ var len = ToLength(O.length);
+
+ /* Step 6. */
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.prototype.find');
+ if (!IsCallable(predicate))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, predicate));
+
+ /* Step 7. */
+ var T = arguments.length > 1 ? arguments[1] : undefined;
+
+ /* Steps 8-9. */
+ /* Steps a (implicit), and g. */
+ for (var k = 0; k < len; k++) {
+ /* Steps a-c. */
+ var kValue = O[k];
+ /* Steps d-f. */
+ if (callContentFunction(predicate, T, kValue, k, O))
+ return kValue;
+ }
+
+ /* Step 10. */
+ return undefined;
+}
+
+/* ES6 draft 2013-05-14 15.4.3.23. */
+function ArrayFindIndex(predicate/*, thisArg*/) {
+ /* Steps 1-2. */
+ var O = ToObject(this);
+
+ /* Steps 3-5. */
+ var len = ToLength(O.length);
+
+ /* Step 6. */
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.prototype.find');
+ if (!IsCallable(predicate))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, predicate));
+
+ /* Step 7. */
+ var T = arguments.length > 1 ? arguments[1] : undefined;
+
+ /* Steps 8-9. */
+ /* Steps a (implicit), and g. */
+ for (var k = 0; k < len; k++) {
+ /* Steps a-f. */
+ if (callContentFunction(predicate, T, O[k], k, O))
+ return k;
+ }
+
+ /* Step 10. */
+ return -1;
+}
+
+/* ES6 draft 2013-09-27 22.1.3.3. */
+function ArrayCopyWithin(target, start, end = undefined) {
+ /* Steps 1-2. */
+ var O = ToObject(this);
+
+ /* Steps 3-5. */
+ var len = ToLength(O.length);
+
+ /* Steps 6-8. */
+ var relativeTarget = ToInteger(target);
+
+ var to = relativeTarget < 0 ? std_Math_max(len + relativeTarget, 0)
+ : std_Math_min(relativeTarget, len);
+
+ /* Steps 9-11. */
+ var relativeStart = ToInteger(start);
+
+ var from = relativeStart < 0 ? std_Math_max(len + relativeStart, 0)
+ : std_Math_min(relativeStart, len);
+
+ /* Steps 12-14. */
+ var relativeEnd = end === undefined ? len
+ : ToInteger(end);
+
+ var final = relativeEnd < 0 ? std_Math_max(len + relativeEnd, 0)
+ : std_Math_min(relativeEnd, len);
+
+ /* Step 15. */
+ var count = std_Math_min(final - from, len - to);
+
+ /* Steps 16-17. */
+ if (from < to && to < (from + count)) {
+ from = from + count - 1;
+ to = to + count - 1;
+ /* Step 18. */
+ while (count > 0) {
+ if (from in O)
+ O[to] = O[from];
+ else
+ delete O[to];
+
+ from--;
+ to--;
+ count--;
+ }
+ } else {
+ /* Step 18. */
+ while (count > 0) {
+ if (from in O)
+ O[to] = O[from];
+ else
+ delete O[to];
+
+ from++;
+ to++;
+ count--;
+ }
+ }
+
+ /* Step 19. */
+ return O;
+}
+
+// ES6 draft 2014-04-05 22.1.3.6
+function ArrayFill(value, start = 0, end = undefined) {
+ // Steps 1-2.
+ var O = ToObject(this);
+
+ // Steps 3-5.
+ var len = ToLength(O.length);
+
+ // Steps 6-7.
+ var relativeStart = ToInteger(start);
+
+ // Step 8.
+ var k = relativeStart < 0
+ ? std_Math_max(len + relativeStart, 0)
+ : std_Math_min(relativeStart, len);
+
+ // Steps 9-10.
+ var relativeEnd = end === undefined ? len : ToInteger(end);
+
+ // Step 11.
+ var final = relativeEnd < 0
+ ? std_Math_max(len + relativeEnd, 0)
+ : std_Math_min(relativeEnd, len);
+
+ // Step 12.
+ for (; k < final; k++) {
+ O[k] = value;
+ }
+
+ // Step 13.
+ return O;
+}
+
+// Proposed for ES7:
+// https://github.com/tc39/Array.prototype.includes/blob/7c023c19a0/spec.md
+function ArrayIncludes(searchElement, fromIndex = 0) {
+ // Steps 1-2.
+ var O = ToObject(this);
+
+ // Steps 3-4.
+ var len = ToLength(O.length);
+
+ // Step 5.
+ if (len === 0)
+ return false;
+
+ // Steps 6-7.
+ var n = ToInteger(fromIndex);
+
+ // Step 8.
+ var k;
+ if (n >= 0) {
+ k = n;
+ }
+ // Step 9.
+ else {
+ // Step a.
+ k = len + n;
+ // Step b.
+ if (k < 0)
+ k = 0;
+ }
+
+ // Step 10.
+ while (k < len) {
+ // Steps a-c.
+ if (SameValueZero(searchElement, O[k]))
+ return true;
+
+ // Step d.
+ k++;
+ }
+
+ // Step 11.
+ return false;
+}
+
+// ES6 draft specification, section 22.1.5.1, version 2013-09-05.
+function CreateArrayIteratorAt(obj, kind, n) {
+ var iteratedObject = ToObject(obj);
+ var iterator = NewArrayIterator();
+ UnsafeSetReservedSlot(iterator, ITERATOR_SLOT_TARGET, iteratedObject);
+ UnsafeSetReservedSlot(iterator, ITERATOR_SLOT_NEXT_INDEX, n);
+ UnsafeSetReservedSlot(iterator, ITERATOR_SLOT_ITEM_KIND, kind);
+ return iterator;
+}
+function CreateArrayIterator(obj, kind) {
+ return CreateArrayIteratorAt(obj, kind, 0);
+}
+
+// ES6, 22.1.5.2.1
+// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-%arrayiteratorprototype%.next
+function ArrayIteratorNext() {
+ // Step 1-3.
+ if (!IsObject(this) || !IsArrayIterator(this)) {
+ return callFunction(CallArrayIteratorMethodIfWrapped, this,
+ "ArrayIteratorNext");
+ }
+
+ // Step 4.
+ var a = UnsafeGetReservedSlot(this, ITERATOR_SLOT_TARGET);
+ var result = { value: undefined, done: false };
+
+ // Step 5.
+ if (a === null) {
+ result.done = true;
+ return result;
+ }
+
+ // Step 6.
+ // The index might not be an integer, so we have to do a generic get here.
+ var index = UnsafeGetReservedSlot(this, ITERATOR_SLOT_NEXT_INDEX);
+
+ // Step 7.
+ var itemKind = UnsafeGetInt32FromReservedSlot(this, ITERATOR_SLOT_ITEM_KIND);
+
+ // Step 8-9.
+ var len = IsPossiblyWrappedTypedArray(a)
+ ? PossiblyWrappedTypedArrayLength(a)
+ : ToLength(a.length);
+
+ // Step 10.
+ if (index >= len) {
+ UnsafeSetReservedSlot(this, ITERATOR_SLOT_TARGET, null);
+ result.done = true;
+ return result;
+ }
+
+ // Step 11.
+ UnsafeSetReservedSlot(this, ITERATOR_SLOT_NEXT_INDEX, index + 1);
+
+ // Step 16.
+ if (itemKind === ITEM_KIND_VALUE) {
+ result.value = a[index];
+ return result;
+ }
+
+ // Step 13.
+ if (itemKind === ITEM_KIND_KEY_AND_VALUE) {
+ var pair = [index, a[index]];
+ result.value = pair;
+ return result;
+ }
+
+ // Step 12.
+ assert(itemKind === ITEM_KIND_KEY, itemKind);
+ result.value = index;
+ return result;
+}
+
+function ArrayValuesAt(n) {
+ return CreateArrayIteratorAt(this, ITEM_KIND_VALUE, n);
+}
+
+function ArrayValues() {
+ return CreateArrayIterator(this, ITEM_KIND_VALUE);
+}
+_SetCanonicalName(ArrayValues, "values");
+
+function ArrayEntries() {
+ return CreateArrayIterator(this, ITEM_KIND_KEY_AND_VALUE);
+}
+
+function ArrayKeys() {
+ return CreateArrayIterator(this, ITEM_KIND_KEY);
+}
+
+// ES6 draft rev31 (2015/01/15) 22.1.2.1 Array.from(source[, mapfn[, thisArg]]).
+function ArrayFrom(items, mapfn=undefined, thisArg=undefined) {
+ // Step 1.
+ var C = this;
+
+ // Steps 2-3.
+ var mapping = mapfn !== undefined;
+ if (mapping && !IsCallable(mapfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, mapfn));
+ var T = thisArg;
+
+ // Steps 4-5.
+ var usingIterator = GetMethod(items, std_iterator);
+
+ // Step 6.
+ if (usingIterator !== undefined) {
+ // Steps 6.a-c.
+ var A = IsConstructor(C) ? new C() : [];
+
+ // Steps 6.d-e.
+ var iterator = GetIterator(items, usingIterator);
+
+ // Step 6.f.
+ var k = 0;
+
+ // Step 6.g.
+ // These steps cannot be implemented using a for-of loop.
+ // See <https://bugs.ecmascript.org/show_bug.cgi?id=2883>.
+ while (true) {
+ // Steps 6.g.i-iii.
+ var next = callContentFunction(iterator.next, iterator);
+ if (!IsObject(next))
+ ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE);
+
+ // Step 6.g.iv.
+ if (next.done) {
+ A.length = k;
+ return A;
+ }
+
+ // Steps 6.g.v-vi.
+ var nextValue = next.value;
+
+ // Steps 6.g.vii-viii.
+ var mappedValue = mapping ? callContentFunction(mapfn, thisArg, nextValue, k) : nextValue;
+
+ // Steps 6.g.ix-xi.
+ _DefineDataProperty(A, k++, mappedValue);
+ }
+ }
+
+ // Step 7.
+ assert(usingIterator === undefined, "`items` can't be an Iterable after step 6.g.iv");
+
+ // Steps 8-9.
+ var arrayLike = ToObject(items);
+
+ // Steps 10-11.
+ var len = ToLength(arrayLike.length);
+
+ // Steps 12-14.
+ var A = IsConstructor(C) ? new C(len) : std_Array(len);
+
+ // Steps 15-16.
+ for (var k = 0; k < len; k++) {
+ // Steps 16.a-c.
+ var kValue = items[k];
+
+ // Steps 16.d-e.
+ var mappedValue = mapping ? callContentFunction(mapfn, thisArg, kValue, k) : kValue;
+
+ // Steps 16.f-g.
+ _DefineDataProperty(A, k, mappedValue);
+ }
+
+ // Steps 17-18.
+ A.length = len;
+
+ // Step 19.
+ return A;
+}
+
+// ES2015 22.1.3.27 Array.prototype.toString.
+function ArrayToString() {
+ // Steps 1-2.
+ var array = ToObject(this);
+
+ // Steps 3-4.
+ var func = array.join;
+
+ // Steps 5-6.
+ if (!IsCallable(func))
+ return callFunction(std_Object_toString, array);
+ return callContentFunction(func, array);
+}
+
+// ES2017 draft rev f8a9be8ea4bd97237d176907a1e3080dce20c68f
+// 22.1.3.27 Array.prototype.toLocaleString ([ reserved1 [ , reserved2 ] ])
+// ES2017 Intl draft rev 78bbe7d1095f5ff3760ac4017ed366026e4cb276
+// 13.4.1 Array.prototype.toLocaleString ([ locales [ , options ]])
+function ArrayToLocaleString(locales, options) {
+ // Step 1 (ToObject already performed in native code).
+ assert(IsObject(this), "|this| should be an object");
+ var array = this;
+
+ // Step 2.
+ var len = ToLength(array.length);
+
+ // Step 4.
+ if (len === 0)
+ return "";
+
+ // Step 5.
+ var firstElement = array[0];
+
+ // Steps 6-7.
+ var R;
+ if (firstElement === undefined || firstElement === null) {
+ R = "";
+ } else {
+#if EXPOSE_INTL_API
+ R = ToString(callContentFunction(firstElement.toLocaleString, firstElement, locales, options));
+#else
+ R = ToString(callContentFunction(firstElement.toLocaleString, firstElement));
+#endif
+ }
+
+ // Step 3 (reordered).
+ // We don't (yet?) implement locale-dependent separators.
+ var separator = ",";
+
+ // Steps 8-9.
+ for (var k = 1; k < len; k++) {
+ // Step 9.b.
+ var nextElement = array[k];
+
+ // Steps 9.a, 9.c-e.
+ R += separator;
+ if (!(nextElement === undefined || nextElement === null)) {
+#if EXPOSE_INTL_API
+ R += ToString(callContentFunction(nextElement.toLocaleString, nextElement, locales, options));
+#else
+ R += ToString(callContentFunction(nextElement.toLocaleString, nextElement));
+#endif
+ }
+ }
+
+ // Step 10.
+ return R;
+}
+
+// ES 2016 draft Mar 25, 2016 22.1.2.5.
+function ArraySpecies() {
+ // Step 1.
+ return this;
+}
+_SetCanonicalName(ArraySpecies, "get [Symbol.species]");
+
+// ES 2016 draft Mar 25, 2016 9.4.2.3.
+function ArraySpeciesCreate(originalArray, length) {
+ // Step 1.
+ assert(typeof length == "number", "length should be a number");
+ assert(length >= 0, "length should be a non-negative number");
+
+ // Step 2.
+ if (length === -0)
+ length = 0;
+
+ // Step 4, 6.
+ if (!IsArray(originalArray))
+ return std_Array(length);
+
+ // Step 5.a.
+ var C = originalArray.constructor;
+
+ // Step 5.b.
+ if (IsConstructor(C) && IsWrappedArrayConstructor(C))
+ return std_Array(length);
+
+ // Step 5.c.
+ if (IsObject(C)) {
+ // Step 5.c.i.
+ C = C[std_species];
+
+ // Optimized path for an ordinary Array.
+ if (C === GetBuiltinConstructor("Array"))
+ return std_Array(length);
+
+ // Step 5.c.ii.
+ if (C === null)
+ return std_Array(length);
+ }
+
+ // Step 6.
+ if (C === undefined)
+ return std_Array(length);
+
+ // Step 7.
+ if (!IsConstructor(C))
+ ThrowTypeError(JSMSG_NOT_CONSTRUCTOR, "constructor property");
+
+ // Step 8.
+ return new C(length);
+}
+
+// ES 2017 draft (April 8, 2016) 22.1.3.1.1
+function IsConcatSpreadable(O) {
+ // Step 1.
+ if (!IsObject(O))
+ return false;
+
+ // Step 2.
+ var spreadable = O[std_isConcatSpreadable];
+
+ // Step 3.
+ if (spreadable !== undefined)
+ return ToBoolean(spreadable);
+
+ // Step 4.
+ return IsArray(O);
+}
+
+// ES 2016 draft Mar 25, 2016 22.1.3.1.
+// Note: Array.prototype.concat.length is 1.
+function ArrayConcat(arg1) {
+ // Step 1.
+ var O = ToObject(this);
+
+ // Step 2.
+ var A = ArraySpeciesCreate(O, 0);
+
+ // Step 3.
+ var n = 0;
+
+ // Step 4 (implicit in |arguments|).
+
+ // Step 5.
+ var i = 0, argsLen = arguments.length;
+
+ // Step 5.a (first element).
+ var E = O;
+
+ var k, len;
+ while (true) {
+ // Steps 5.b-c.
+ if (IsConcatSpreadable(E)) {
+ // Step 5.c.ii.
+ len = ToLength(E.length);
+
+ // Step 5.c.iii.
+ if (n + len > MAX_NUMERIC_INDEX)
+ ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
+
+ if (IsPackedArray(A) && IsPackedArray(E)) {
+ // Step 5.c.i, 5.c.iv, and 5.c.iv.5.
+ for (k = 0; k < len; k++) {
+ // Steps 5.c.iv.1-3.
+ // IsPackedArray(E) ensures that |k in E| is always true.
+ _DefineDataProperty(A, n, E[k]);
+
+ // Step 5.c.iv.4.
+ n++;
+ }
+ } else {
+ // Step 5.c.i, 5.c.iv, and 5.c.iv.5.
+ for (k = 0; k < len; k++) {
+ // Steps 5.c.iv.1-3.
+ if (k in E)
+ _DefineDataProperty(A, n, E[k]);
+
+ // Step 5.c.iv.4.
+ n++;
+ }
+ }
+ } else {
+ // Step 5.d.i.
+ if (n >= MAX_NUMERIC_INDEX)
+ ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
+
+ // Step 5.d.ii.
+ _DefineDataProperty(A, n, E);
+
+ // Step 5.d.iii.
+ n++;
+ }
+
+ if (i >= argsLen)
+ break;
+ // Step 5.a (subsequent elements).
+ E = arguments[i];
+ i++;
+ }
+
+ // Step 6.
+ A.length = n;
+
+ // Step 7.
+ return A;
+}
+
+function ArrayStaticConcat(arr, arg1) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.concat');
+ var args = callFunction(std_Array_slice, arguments, 1);
+ return callFunction(std_Function_apply, ArrayConcat, arr, args);
+}
+
+function ArrayStaticJoin(arr, separator) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.join');
+ return callFunction(std_Array_join, arr, separator);
+}
+
+function ArrayStaticReverse(arr) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.reverse');
+ return callFunction(std_Array_reverse, arr);
+}
+
+function ArrayStaticSort(arr, comparefn) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.sort');
+ return callFunction(std_Array_sort, arr, comparefn);
+}
+
+function ArrayStaticPush(arr, arg1) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.push');
+ var args = callFunction(std_Array_slice, arguments, 1);
+ return callFunction(std_Function_apply, std_Array_push, arr, args);
+}
+
+function ArrayStaticPop(arr) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.pop');
+ return callFunction(std_Array_pop, arr);
+}
+
+function ArrayStaticShift(arr) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.shift');
+ return callFunction(std_Array_shift, arr);
+}
+
+function ArrayStaticUnshift(arr, arg1) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.unshift');
+ var args = callFunction(std_Array_slice, arguments, 1);
+ return callFunction(std_Function_apply, std_Array_unshift, arr, args);
+}
+
+function ArrayStaticSplice(arr, start, deleteCount) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.splice');
+ var args = callFunction(std_Array_slice, arguments, 1);
+ return callFunction(std_Function_apply, std_Array_splice, arr, args);
+}
+
+function ArrayStaticSlice(arr, start, end) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.slice');
+ return callFunction(std_Array_slice, arr, start, end);
+}
diff --git a/js/src/builtin/AtomicsObject.cpp b/js/src/builtin/AtomicsObject.cpp
new file mode 100644
index 000000000..08777fd51
--- /dev/null
+++ b/js/src/builtin/AtomicsObject.cpp
@@ -0,0 +1,1152 @@
+/* -*- 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/. */
+
+/*
+ * JS Atomics pseudo-module.
+ *
+ * See "Spec: JavaScript Shared Memory, Atomics, and Locks" for the
+ * full specification.
+ *
+ * In addition to what is specified there, we throw an Error object if
+ * the futex API hooks have not been installed on the runtime.
+ * Essentially that is an implementation error at a higher level.
+ *
+ *
+ * Note on the current implementation of atomic operations.
+ *
+ * The Mozilla atomics are not sufficient to implement these APIs
+ * because we need to support 8-bit, 16-bit, and 32-bit data: the
+ * Mozilla atomics only support 32-bit data.
+ *
+ * At the moment we include mozilla/Atomics.h, which will define
+ * MOZ_HAVE_CXX11_ATOMICS and include <atomic> if we have C++11
+ * atomics.
+ *
+ * If MOZ_HAVE_CXX11_ATOMICS is set we'll use C++11 atomics.
+ *
+ * Otherwise, if the compiler has them we'll fall back on gcc/Clang
+ * intrinsics.
+ *
+ * Otherwise, if we're on VC++2012, we'll use C++11 atomics even if
+ * MOZ_HAVE_CXX11_ATOMICS is not defined. The compiler has the
+ * atomics but they are disabled in Mozilla due to a performance bug.
+ * That performance bug does not affect the Atomics code. See
+ * mozilla/Atomics.h for further comments on that bug.
+ *
+ * Otherwise, if we're on VC++2010 or VC++2008, we'll emulate the
+ * gcc/Clang intrinsics with simple code below using the VC++
+ * intrinsics, like the VC++2012 solution this is a stopgap since
+ * we're about to start using VC++2013 anyway.
+ *
+ * If none of those options are available then the build must disable
+ * shared memory, or compilation will fail with a predictable error.
+ */
+
+#include "builtin/AtomicsObject.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Unused.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "jsnum.h"
+
+#include "jit/AtomicOperations.h"
+#include "jit/InlinableNatives.h"
+#include "js/Class.h"
+#include "vm/GlobalObject.h"
+#include "vm/Time.h"
+#include "vm/TypedArrayObject.h"
+#include "wasm/WasmInstance.h"
+
+#include "jsobjinlines.h"
+
+using namespace js;
+
+const Class AtomicsObject::class_ = {
+ "Atomics",
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Atomics)
+};
+
+static bool
+ReportBadArrayType(JSContext* cx)
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ATOMICS_BAD_ARRAY);
+ return false;
+}
+
+static bool
+ReportOutOfRange(JSContext* cx)
+{
+ // Use JSMSG_BAD_INDEX here even if it is generic, since that is
+ // the message used by ToIntegerIndex for its initial range
+ // checking.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
+ return false;
+}
+
+static bool
+ReportCannotWait(JSContext* cx)
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ATOMICS_WAIT_NOT_ALLOWED);
+ return false;
+}
+
+static bool
+GetSharedTypedArray(JSContext* cx, HandleValue v,
+ MutableHandle<TypedArrayObject*> viewp)
+{
+ if (!v.isObject())
+ return ReportBadArrayType(cx);
+ if (!v.toObject().is<TypedArrayObject>())
+ return ReportBadArrayType(cx);
+ viewp.set(&v.toObject().as<TypedArrayObject>());
+ if (!viewp->isSharedMemory())
+ return ReportBadArrayType(cx);
+ return true;
+}
+
+static bool
+GetTypedArrayIndex(JSContext* cx, HandleValue v, Handle<TypedArrayObject*> view, uint32_t* offset)
+{
+ uint64_t index;
+ if (!js::ToIntegerIndex(cx, v, &index))
+ return false;
+ if (index >= view->length())
+ return ReportOutOfRange(cx);
+ *offset = uint32_t(index);
+ return true;
+}
+
+static int32_t
+CompareExchange(Scalar::Type viewType, int32_t oldCandidate, int32_t newCandidate,
+ SharedMem<void*> viewData, uint32_t offset, bool* badArrayType = nullptr)
+{
+ switch (viewType) {
+ case Scalar::Int8: {
+ int8_t oldval = (int8_t)oldCandidate;
+ int8_t newval = (int8_t)newCandidate;
+ oldval = jit::AtomicOperations::compareExchangeSeqCst(viewData.cast<int8_t*>() + offset,
+ oldval, newval);
+ return oldval;
+ }
+ case Scalar::Uint8: {
+ uint8_t oldval = (uint8_t)oldCandidate;
+ uint8_t newval = (uint8_t)newCandidate;
+ oldval = jit::AtomicOperations::compareExchangeSeqCst(viewData.cast<uint8_t*>() + offset,
+ oldval, newval);
+ return oldval;
+ }
+ case Scalar::Int16: {
+ int16_t oldval = (int16_t)oldCandidate;
+ int16_t newval = (int16_t)newCandidate;
+ oldval = jit::AtomicOperations::compareExchangeSeqCst(viewData.cast<int16_t*>() + offset,
+ oldval, newval);
+ return oldval;
+ }
+ case Scalar::Uint16: {
+ uint16_t oldval = (uint16_t)oldCandidate;
+ uint16_t newval = (uint16_t)newCandidate;
+ oldval = jit::AtomicOperations::compareExchangeSeqCst(viewData.cast<uint16_t*>() + offset,
+ oldval, newval);
+ return oldval;
+ }
+ case Scalar::Int32: {
+ int32_t oldval = oldCandidate;
+ int32_t newval = newCandidate;
+ oldval = jit::AtomicOperations::compareExchangeSeqCst(viewData.cast<int32_t*>() + offset,
+ oldval, newval);
+ return oldval;
+ }
+ case Scalar::Uint32: {
+ uint32_t oldval = (uint32_t)oldCandidate;
+ uint32_t newval = (uint32_t)newCandidate;
+ oldval = jit::AtomicOperations::compareExchangeSeqCst(viewData.cast<uint32_t*>() + offset,
+ oldval, newval);
+ return (int32_t)oldval;
+ }
+ default:
+ if (badArrayType)
+ *badArrayType = true;
+ return 0;
+ }
+}
+
+bool
+js::atomics_compareExchange(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue objv = args.get(0);
+ HandleValue idxv = args.get(1);
+ HandleValue oldv = args.get(2);
+ HandleValue newv = args.get(3);
+ MutableHandleValue r = args.rval();
+
+ Rooted<TypedArrayObject*> view(cx, nullptr);
+ if (!GetSharedTypedArray(cx, objv, &view))
+ return false;
+ uint32_t offset;
+ if (!GetTypedArrayIndex(cx, idxv, view, &offset))
+ return false;
+ int32_t oldCandidate;
+ if (!ToInt32(cx, oldv, &oldCandidate))
+ return false;
+ int32_t newCandidate;
+ if (!ToInt32(cx, newv, &newCandidate))
+ return false;
+
+ bool badType = false;
+ int32_t result = CompareExchange(view->type(), oldCandidate, newCandidate,
+ view->viewDataShared(), offset, &badType);
+
+ if (badType)
+ return ReportBadArrayType(cx);
+
+ if (view->type() == Scalar::Uint32)
+ r.setNumber((double)(uint32_t)result);
+ else
+ r.setInt32(result);
+ return true;
+}
+
+bool
+js::atomics_load(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue objv = args.get(0);
+ HandleValue idxv = args.get(1);
+ MutableHandleValue r = args.rval();
+
+ Rooted<TypedArrayObject*> view(cx, nullptr);
+ if (!GetSharedTypedArray(cx, objv, &view))
+ return false;
+ uint32_t offset;
+ if (!GetTypedArrayIndex(cx, idxv, view, &offset))
+ return false;
+
+ SharedMem<void*> viewData = view->viewDataShared();
+ switch (view->type()) {
+ case Scalar::Uint8: {
+ uint8_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<uint8_t*>() + offset);
+ r.setInt32(v);
+ return true;
+ }
+ case Scalar::Int8: {
+ int8_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<uint8_t*>() + offset);
+ r.setInt32(v);
+ return true;
+ }
+ case Scalar::Int16: {
+ int16_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<int16_t*>() + offset);
+ r.setInt32(v);
+ return true;
+ }
+ case Scalar::Uint16: {
+ uint16_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<uint16_t*>() + offset);
+ r.setInt32(v);
+ return true;
+ }
+ case Scalar::Int32: {
+ int32_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<int32_t*>() + offset);
+ r.setInt32(v);
+ return true;
+ }
+ case Scalar::Uint32: {
+ uint32_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<uint32_t*>() + offset);
+ r.setNumber(v);
+ return true;
+ }
+ default:
+ return ReportBadArrayType(cx);
+ }
+}
+
+enum XchgStoreOp {
+ DoExchange,
+ DoStore
+};
+
+template<XchgStoreOp op>
+static int32_t
+ExchangeOrStore(Scalar::Type viewType, int32_t numberValue, SharedMem<void*> viewData,
+ uint32_t offset, bool* badArrayType = nullptr)
+{
+#define INT_OP(ptr, value) \
+ JS_BEGIN_MACRO \
+ if (op == DoStore) \
+ jit::AtomicOperations::storeSeqCst(ptr, value); \
+ else \
+ value = jit::AtomicOperations::exchangeSeqCst(ptr, value); \
+ JS_END_MACRO
+
+ switch (viewType) {
+ case Scalar::Int8: {
+ int8_t value = (int8_t)numberValue;
+ INT_OP(viewData.cast<int8_t*>() + offset, value);
+ return value;
+ }
+ case Scalar::Uint8: {
+ uint8_t value = (uint8_t)numberValue;
+ INT_OP(viewData.cast<uint8_t*>() + offset, value);
+ return value;
+ }
+ case Scalar::Int16: {
+ int16_t value = (int16_t)numberValue;
+ INT_OP(viewData.cast<int16_t*>() + offset, value);
+ return value;
+ }
+ case Scalar::Uint16: {
+ uint16_t value = (uint16_t)numberValue;
+ INT_OP(viewData.cast<uint16_t*>() + offset, value);
+ return value;
+ }
+ case Scalar::Int32: {
+ int32_t value = numberValue;
+ INT_OP(viewData.cast<int32_t*>() + offset, value);
+ return value;
+ }
+ case Scalar::Uint32: {
+ uint32_t value = (uint32_t)numberValue;
+ INT_OP(viewData.cast<uint32_t*>() + offset, value);
+ return (int32_t)value;
+ }
+ default:
+ if (badArrayType)
+ *badArrayType = true;
+ return 0;
+ }
+#undef INT_OP
+}
+
+template<XchgStoreOp op>
+static bool
+ExchangeOrStore(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue objv = args.get(0);
+ HandleValue idxv = args.get(1);
+ HandleValue valv = args.get(2);
+ MutableHandleValue r = args.rval();
+
+ Rooted<TypedArrayObject*> view(cx, nullptr);
+ if (!GetSharedTypedArray(cx, objv, &view))
+ return false;
+ uint32_t offset;
+ if (!GetTypedArrayIndex(cx, idxv, view, &offset))
+ return false;
+ double integerValue;
+ if (!ToInteger(cx, valv, &integerValue))
+ return false;
+
+ bool badType = false;
+ int32_t result = ExchangeOrStore<op>(view->type(), JS::ToInt32(integerValue),
+ view->viewDataShared(), offset, &badType);
+
+ if (badType)
+ return ReportBadArrayType(cx);
+
+ if (op == DoStore)
+ r.setNumber(integerValue);
+ else if (view->type() == Scalar::Uint32)
+ r.setNumber((double)(uint32_t)result);
+ else
+ r.setInt32(result);
+ return true;
+}
+
+bool
+js::atomics_store(JSContext* cx, unsigned argc, Value* vp)
+{
+ return ExchangeOrStore<DoStore>(cx, argc, vp);
+}
+
+bool
+js::atomics_exchange(JSContext* cx, unsigned argc, Value* vp)
+{
+ return ExchangeOrStore<DoExchange>(cx, argc, vp);
+}
+
+template<typename T>
+static bool
+AtomicsBinop(JSContext* cx, HandleValue objv, HandleValue idxv, HandleValue valv,
+ MutableHandleValue r)
+{
+ Rooted<TypedArrayObject*> view(cx, nullptr);
+ if (!GetSharedTypedArray(cx, objv, &view))
+ return false;
+ uint32_t offset;
+ if (!GetTypedArrayIndex(cx, idxv, view, &offset))
+ return false;
+ int32_t numberValue;
+ if (!ToInt32(cx, valv, &numberValue))
+ return false;
+
+ SharedMem<void*> viewData = view->viewDataShared();
+ switch (view->type()) {
+ case Scalar::Int8: {
+ int8_t v = (int8_t)numberValue;
+ r.setInt32(T::operate(viewData.cast<int8_t*>() + offset, v));
+ return true;
+ }
+ case Scalar::Uint8: {
+ uint8_t v = (uint8_t)numberValue;
+ r.setInt32(T::operate(viewData.cast<uint8_t*>() + offset, v));
+ return true;
+ }
+ case Scalar::Int16: {
+ int16_t v = (int16_t)numberValue;
+ r.setInt32(T::operate(viewData.cast<int16_t*>() + offset, v));
+ return true;
+ }
+ case Scalar::Uint16: {
+ uint16_t v = (uint16_t)numberValue;
+ r.setInt32(T::operate(viewData.cast<uint16_t*>() + offset, v));
+ return true;
+ }
+ case Scalar::Int32: {
+ int32_t v = numberValue;
+ r.setInt32(T::operate(viewData.cast<int32_t*>() + offset, v));
+ return true;
+ }
+ case Scalar::Uint32: {
+ uint32_t v = (uint32_t)numberValue;
+ r.setNumber((double)T::operate(viewData.cast<uint32_t*>() + offset, v));
+ return true;
+ }
+ default:
+ return ReportBadArrayType(cx);
+ }
+}
+
+#define INTEGRAL_TYPES_FOR_EACH(NAME) \
+ static int8_t operate(SharedMem<int8_t*> addr, int8_t v) { return NAME(addr, v); } \
+ static uint8_t operate(SharedMem<uint8_t*> addr, uint8_t v) { return NAME(addr, v); } \
+ static int16_t operate(SharedMem<int16_t*> addr, int16_t v) { return NAME(addr, v); } \
+ static uint16_t operate(SharedMem<uint16_t*> addr, uint16_t v) { return NAME(addr, v); } \
+ static int32_t operate(SharedMem<int32_t*> addr, int32_t v) { return NAME(addr, v); } \
+ static uint32_t operate(SharedMem<uint32_t*> addr, uint32_t v) { return NAME(addr, v); }
+
+class PerformAdd
+{
+public:
+ INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAddSeqCst)
+ static int32_t perform(int32_t x, int32_t y) { return x + y; }
+};
+
+bool
+js::atomics_add(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return AtomicsBinop<PerformAdd>(cx, args.get(0), args.get(1), args.get(2), args.rval());
+}
+
+class PerformSub
+{
+public:
+ INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchSubSeqCst)
+ static int32_t perform(int32_t x, int32_t y) { return x - y; }
+};
+
+bool
+js::atomics_sub(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return AtomicsBinop<PerformSub>(cx, args.get(0), args.get(1), args.get(2), args.rval());
+}
+
+class PerformAnd
+{
+public:
+ INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAndSeqCst)
+ static int32_t perform(int32_t x, int32_t y) { return x & y; }
+};
+
+bool
+js::atomics_and(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return AtomicsBinop<PerformAnd>(cx, args.get(0), args.get(1), args.get(2), args.rval());
+}
+
+class PerformOr
+{
+public:
+ INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchOrSeqCst)
+ static int32_t perform(int32_t x, int32_t y) { return x | y; }
+};
+
+bool
+js::atomics_or(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return AtomicsBinop<PerformOr>(cx, args.get(0), args.get(1), args.get(2), args.rval());
+}
+
+class PerformXor
+{
+public:
+ INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchXorSeqCst)
+ static int32_t perform(int32_t x, int32_t y) { return x ^ y; }
+};
+
+bool
+js::atomics_xor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return AtomicsBinop<PerformXor>(cx, args.get(0), args.get(1), args.get(2), args.rval());
+}
+
+bool
+js::atomics_isLockFree(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue v = args.get(0);
+ int32_t size;
+ if (v.isInt32()) {
+ size = v.toInt32();
+ } else {
+ double dsize;
+ if (!ToInteger(cx, v, &dsize))
+ return false;
+ if (!mozilla::NumberIsInt32(dsize, &size)) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+ }
+ args.rval().setBoolean(jit::AtomicOperations::isLockfree(size));
+ return true;
+}
+
+// asm.js callouts for platforms that do not have non-word-sized
+// atomics where we don't want to inline the logic for the atomics.
+//
+// Memory will always be shared since the callouts are only called from
+// code that checks that the memory is shared.
+//
+// To test this, either run on eg Raspberry Pi Model 1, or invoke the ARM
+// simulator build with ARMHWCAP=vfp set. Do not set any other flags; other
+// vfp/neon flags force ARMv7 to be set.
+
+int32_t
+js::atomics_add_asm_callout(wasm::Instance* instance, int32_t vt, int32_t offset, int32_t value)
+{
+ SharedMem<void*> heap = instance->memoryBase().cast<void*>();
+ size_t heapLength = instance->memoryLength();
+
+ if (size_t(offset) >= heapLength)
+ return 0;
+
+ switch (Scalar::Type(vt)) {
+ case Scalar::Int8:
+ return PerformAdd::operate(heap.cast<int8_t*>() + offset, value);
+ case Scalar::Uint8:
+ return PerformAdd::operate(heap.cast<uint8_t*>() + offset, value);
+ case Scalar::Int16:
+ return PerformAdd::operate(heap.cast<int16_t*>() + (offset >> 1), value);
+ case Scalar::Uint16:
+ return PerformAdd::operate(heap.cast<uint16_t*>() + (offset >> 1), value);
+ default:
+ MOZ_CRASH("Invalid size");
+ }
+}
+
+int32_t
+js::atomics_sub_asm_callout(wasm::Instance* instance, int32_t vt, int32_t offset, int32_t value)
+{
+ SharedMem<void*> heap = instance->memoryBase().cast<void*>();
+ size_t heapLength = instance->memoryLength();
+
+ if (size_t(offset) >= heapLength)
+ return 0;
+
+ switch (Scalar::Type(vt)) {
+ case Scalar::Int8:
+ return PerformSub::operate(heap.cast<int8_t*>() + offset, value);
+ case Scalar::Uint8:
+ return PerformSub::operate(heap.cast<uint8_t*>() + offset, value);
+ case Scalar::Int16:
+ return PerformSub::operate(heap.cast<int16_t*>() + (offset >> 1), value);
+ case Scalar::Uint16:
+ return PerformSub::operate(heap.cast<uint16_t*>() + (offset >> 1), value);
+ default:
+ MOZ_CRASH("Invalid size");
+ }
+}
+
+int32_t
+js::atomics_and_asm_callout(wasm::Instance* instance, int32_t vt, int32_t offset, int32_t value)
+{
+ SharedMem<void*> heap = instance->memoryBase().cast<void*>();
+ size_t heapLength = instance->memoryLength();
+
+ if (size_t(offset) >= heapLength)
+ return 0;
+
+ switch (Scalar::Type(vt)) {
+ case Scalar::Int8:
+ return PerformAnd::operate(heap.cast<int8_t*>() + offset, value);
+ case Scalar::Uint8:
+ return PerformAnd::operate(heap.cast<uint8_t*>() + offset, value);
+ case Scalar::Int16:
+ return PerformAnd::operate(heap.cast<int16_t*>() + (offset >> 1), value);
+ case Scalar::Uint16:
+ return PerformAnd::operate(heap.cast<uint16_t*>() + (offset >> 1), value);
+ default:
+ MOZ_CRASH("Invalid size");
+ }
+}
+
+int32_t
+js::atomics_or_asm_callout(wasm::Instance* instance, int32_t vt, int32_t offset, int32_t value)
+{
+ SharedMem<void*> heap = instance->memoryBase().cast<void*>();
+ size_t heapLength = instance->memoryLength();
+
+ if (size_t(offset) >= heapLength)
+ return 0;
+
+ switch (Scalar::Type(vt)) {
+ case Scalar::Int8:
+ return PerformOr::operate(heap.cast<int8_t*>() + offset, value);
+ case Scalar::Uint8:
+ return PerformOr::operate(heap.cast<uint8_t*>() + offset, value);
+ case Scalar::Int16:
+ return PerformOr::operate(heap.cast<int16_t*>() + (offset >> 1), value);
+ case Scalar::Uint16:
+ return PerformOr::operate(heap.cast<uint16_t*>() + (offset >> 1), value);
+ default:
+ MOZ_CRASH("Invalid size");
+ }
+}
+
+int32_t
+js::atomics_xor_asm_callout(wasm::Instance* instance, int32_t vt, int32_t offset, int32_t value)
+{
+ SharedMem<void*> heap = instance->memoryBase().cast<void*>();
+ size_t heapLength = instance->memoryLength();
+
+ if (size_t(offset) >= heapLength)
+ return 0;
+
+ switch (Scalar::Type(vt)) {
+ case Scalar::Int8:
+ return PerformXor::operate(heap.cast<int8_t*>() + offset, value);
+ case Scalar::Uint8:
+ return PerformXor::operate(heap.cast<uint8_t*>() + offset, value);
+ case Scalar::Int16:
+ return PerformXor::operate(heap.cast<int16_t*>() + (offset >> 1), value);
+ case Scalar::Uint16:
+ return PerformXor::operate(heap.cast<uint16_t*>() + (offset >> 1), value);
+ default:
+ MOZ_CRASH("Invalid size");
+ }
+}
+
+int32_t
+js::atomics_xchg_asm_callout(wasm::Instance* instance, int32_t vt, int32_t offset, int32_t value)
+{
+ SharedMem<void*> heap = instance->memoryBase().cast<void*>();
+ size_t heapLength = instance->memoryLength();
+
+ if (size_t(offset) >= heapLength)
+ return 0;
+
+ switch (Scalar::Type(vt)) {
+ case Scalar::Int8:
+ return ExchangeOrStore<DoExchange>(Scalar::Int8, value, heap, offset);
+ case Scalar::Uint8:
+ return ExchangeOrStore<DoExchange>(Scalar::Uint8, value, heap, offset);
+ case Scalar::Int16:
+ return ExchangeOrStore<DoExchange>(Scalar::Int16, value, heap, offset>>1);
+ case Scalar::Uint16:
+ return ExchangeOrStore<DoExchange>(Scalar::Uint16, value, heap, offset>>1);
+ default:
+ MOZ_CRASH("Invalid size");
+ }
+}
+
+int32_t
+js::atomics_cmpxchg_asm_callout(wasm::Instance* instance, int32_t vt, int32_t offset, int32_t oldval, int32_t newval)
+{
+ SharedMem<void*> heap = instance->memoryBase().cast<void*>();
+ size_t heapLength = instance->memoryLength();
+
+ if (size_t(offset) >= heapLength)
+ return 0;
+
+ switch (Scalar::Type(vt)) {
+ case Scalar::Int8:
+ return CompareExchange(Scalar::Int8, oldval, newval, heap, offset);
+ case Scalar::Uint8:
+ return CompareExchange(Scalar::Uint8, oldval, newval, heap, offset);
+ case Scalar::Int16:
+ return CompareExchange(Scalar::Int16, oldval, newval, heap, offset>>1);
+ case Scalar::Uint16:
+ return CompareExchange(Scalar::Uint16, oldval, newval, heap, offset>>1);
+ default:
+ MOZ_CRASH("Invalid size");
+ }
+}
+
+namespace js {
+
+// Represents one waiting worker.
+//
+// The type is declared opaque in SharedArrayObject.h. Instances of
+// js::FutexWaiter are stack-allocated and linked onto a list across a
+// call to FutexRuntime::wait().
+//
+// The 'waiters' field of the SharedArrayRawBuffer points to the highest
+// priority waiter in the list, and lower priority nodes are linked through
+// the 'lower_pri' field. The 'back' field goes the other direction.
+// The list is circular, so the 'lower_pri' field of the lowest priority
+// node points to the first node in the list. The list has no dedicated
+// header node.
+
+class FutexWaiter
+{
+ public:
+ FutexWaiter(uint32_t offset, JSRuntime* rt)
+ : offset(offset),
+ rt(rt),
+ lower_pri(nullptr),
+ back(nullptr)
+ {
+ }
+
+ uint32_t offset; // int32 element index within the SharedArrayBuffer
+ JSRuntime* rt; // The runtime of the waiter
+ FutexWaiter* lower_pri; // Lower priority nodes in circular doubly-linked list of waiters
+ FutexWaiter* back; // Other direction
+};
+
+class AutoLockFutexAPI
+{
+ // We have to wrap this in a Maybe because of the way loading
+ // mozilla::Atomic pointers works.
+ mozilla::Maybe<js::UniqueLock<js::Mutex>> unique_;
+
+ public:
+ AutoLockFutexAPI() {
+ js::Mutex* lock = FutexRuntime::lock_;
+ unique_.emplace(*lock);
+ }
+
+ ~AutoLockFutexAPI() {
+ unique_.reset();
+ }
+
+ js::UniqueLock<js::Mutex>& unique() { return *unique_; }
+};
+
+} // namespace js
+
+bool
+js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue objv = args.get(0);
+ HandleValue idxv = args.get(1);
+ HandleValue valv = args.get(2);
+ HandleValue timeoutv = args.get(3);
+ MutableHandleValue r = args.rval();
+
+ JSRuntime* rt = cx->runtime();
+
+ Rooted<TypedArrayObject*> view(cx, nullptr);
+ if (!GetSharedTypedArray(cx, objv, &view))
+ return false;
+ if (view->type() != Scalar::Int32)
+ return ReportBadArrayType(cx);
+ uint32_t offset;
+ if (!GetTypedArrayIndex(cx, idxv, view, &offset))
+ return false;
+ int32_t value;
+ if (!ToInt32(cx, valv, &value))
+ return false;
+ mozilla::Maybe<mozilla::TimeDuration> timeout;
+ if (!timeoutv.isUndefined()) {
+ double timeout_ms;
+ if (!ToNumber(cx, timeoutv, &timeout_ms))
+ return false;
+ if (!mozilla::IsNaN(timeout_ms)) {
+ if (timeout_ms < 0)
+ timeout = mozilla::Some(mozilla::TimeDuration::FromSeconds(0.0));
+ else if (!mozilla::IsInfinite(timeout_ms))
+ timeout = mozilla::Some(mozilla::TimeDuration::FromMilliseconds(timeout_ms));
+ }
+ }
+
+ if (!rt->fx.canWait())
+ return ReportCannotWait(cx);
+
+ // This lock also protects the "waiters" field on SharedArrayRawBuffer,
+ // and it provides the necessary memory fence.
+ AutoLockFutexAPI lock;
+
+ SharedMem<int32_t*>(addr) = view->viewDataShared().cast<int32_t*>() + offset;
+ if (jit::AtomicOperations::loadSafeWhenRacy(addr) != value) {
+ r.setString(cx->names().futexNotEqual);
+ return true;
+ }
+
+ Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
+ SharedArrayRawBuffer* sarb = sab->rawBufferObject();
+
+ FutexWaiter w(offset, rt);
+ if (FutexWaiter* waiters = sarb->waiters()) {
+ w.lower_pri = waiters;
+ w.back = waiters->back;
+ waiters->back->lower_pri = &w;
+ waiters->back = &w;
+ } else {
+ w.lower_pri = w.back = &w;
+ sarb->setWaiters(&w);
+ }
+
+ FutexRuntime::WaitResult result = FutexRuntime::FutexOK;
+ bool retval = rt->fx.wait(cx, lock.unique(), timeout, &result);
+ if (retval) {
+ switch (result) {
+ case FutexRuntime::FutexOK:
+ r.setString(cx->names().futexOK);
+ break;
+ case FutexRuntime::FutexTimedOut:
+ r.setString(cx->names().futexTimedOut);
+ break;
+ }
+ }
+
+ if (w.lower_pri == &w) {
+ sarb->setWaiters(nullptr);
+ } else {
+ w.lower_pri->back = w.back;
+ w.back->lower_pri = w.lower_pri;
+ if (sarb->waiters() == &w)
+ sarb->setWaiters(w.lower_pri);
+ }
+ return retval;
+}
+
+bool
+js::atomics_wake(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue objv = args.get(0);
+ HandleValue idxv = args.get(1);
+ HandleValue countv = args.get(2);
+ MutableHandleValue r = args.rval();
+
+ Rooted<TypedArrayObject*> view(cx, nullptr);
+ if (!GetSharedTypedArray(cx, objv, &view))
+ return false;
+ if (view->type() != Scalar::Int32)
+ return ReportBadArrayType(cx);
+ uint32_t offset;
+ if (!GetTypedArrayIndex(cx, idxv, view, &offset))
+ return false;
+ double count;
+ if (countv.isUndefined()) {
+ count = mozilla::PositiveInfinity<double>();
+ } else {
+ if (!ToInteger(cx, countv, &count))
+ return false;
+ if (count < 0.0)
+ count = 0.0;
+ }
+
+ AutoLockFutexAPI lock;
+
+ Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
+ SharedArrayRawBuffer* sarb = sab->rawBufferObject();
+ int32_t woken = 0;
+
+ FutexWaiter* waiters = sarb->waiters();
+ if (waiters && count > 0) {
+ FutexWaiter* iter = waiters;
+ do {
+ FutexWaiter* c = iter;
+ iter = iter->lower_pri;
+ if (c->offset != offset || !c->rt->fx.isWaiting())
+ continue;
+ c->rt->fx.wake(FutexRuntime::WakeExplicit);
+ ++woken;
+ --count;
+ } while (count > 0 && iter != waiters);
+ }
+
+ r.setInt32(woken);
+ return true;
+}
+
+/* static */ bool
+js::FutexRuntime::initialize()
+{
+ MOZ_ASSERT(!lock_);
+ lock_ = js_new<js::Mutex>(mutexid::FutexRuntime);
+ return lock_ != nullptr;
+}
+
+/* static */ void
+js::FutexRuntime::destroy()
+{
+ if (lock_) {
+ js::Mutex* lock = lock_;
+ js_delete(lock);
+ lock_ = nullptr;
+ }
+}
+
+/* static */ void
+js::FutexRuntime::lock()
+{
+ // Load the atomic pointer.
+ js::Mutex* lock = lock_;
+
+ lock->lock();
+}
+
+/* static */ mozilla::Atomic<js::Mutex*> FutexRuntime::lock_;
+
+/* static */ void
+js::FutexRuntime::unlock()
+{
+ // Load the atomic pointer.
+ js::Mutex* lock = lock_;
+
+ lock->unlock();
+}
+
+js::FutexRuntime::FutexRuntime()
+ : cond_(nullptr),
+ state_(Idle),
+ canWait_(false)
+{
+}
+
+bool
+js::FutexRuntime::initInstance()
+{
+ MOZ_ASSERT(lock_);
+ cond_ = js_new<js::ConditionVariable>();
+ return cond_ != nullptr;
+}
+
+void
+js::FutexRuntime::destroyInstance()
+{
+ if (cond_)
+ js_delete(cond_);
+}
+
+bool
+js::FutexRuntime::isWaiting()
+{
+ // When a worker is awoken for an interrupt it goes into state
+ // WaitingNotifiedForInterrupt for a short time before it actually
+ // wakes up and goes into WaitingInterrupted. In those states the
+ // worker is still waiting, and if an explicit wake arrives the
+ // worker transitions to Woken. See further comments in
+ // FutexRuntime::wait().
+ return state_ == Waiting || state_ == WaitingInterrupted || state_ == WaitingNotifiedForInterrupt;
+}
+
+bool
+js::FutexRuntime::wait(JSContext* cx, js::UniqueLock<js::Mutex>& locked,
+ mozilla::Maybe<mozilla::TimeDuration>& timeout, WaitResult* result)
+{
+ MOZ_ASSERT(&cx->runtime()->fx == this);
+ MOZ_ASSERT(cx->runtime()->fx.canWait());
+ MOZ_ASSERT(state_ == Idle || state_ == WaitingInterrupted);
+
+ // Disallow waiting when a runtime is processing an interrupt.
+ // See explanation below.
+
+ if (state_ == WaitingInterrupted) {
+ UnlockGuard<Mutex> unlock(locked);
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ATOMICS_WAIT_NOT_ALLOWED);
+ return false;
+ }
+
+ const bool isTimed = timeout.isSome();
+
+ auto finalEnd = timeout.map([](mozilla::TimeDuration& timeout) {
+ return mozilla::TimeStamp::Now() + timeout;
+ });
+
+
+ // 4000s is about the longest timeout slice that is guaranteed to
+ // work cross-platform.
+ auto maxSlice = mozilla::TimeDuration::FromSeconds(4000.0);
+
+ bool retval = true;
+
+ for (;;) {
+ // If we are doing a timed wait, calculate the end time for this wait
+ // slice.
+ auto sliceEnd = finalEnd.map([&](mozilla::TimeStamp& finalEnd) {
+ auto sliceEnd = mozilla::TimeStamp::Now() + maxSlice;
+ if (finalEnd < sliceEnd)
+ sliceEnd = finalEnd;
+ return sliceEnd;
+ });
+
+ state_ = Waiting;
+
+ if (isTimed) {
+ mozilla::Unused << cond_->wait_until(locked, *sliceEnd);
+ } else {
+ cond_->wait(locked);
+ }
+
+ switch (state_) {
+ case FutexRuntime::Waiting:
+ // Timeout or spurious wakeup.
+ if (isTimed) {
+ auto now = mozilla::TimeStamp::Now();
+ if (now >= *finalEnd) {
+ *result = FutexTimedOut;
+ goto finished;
+ }
+ }
+ break;
+
+ case FutexRuntime::Woken:
+ *result = FutexOK;
+ goto finished;
+
+ case FutexRuntime::WaitingNotifiedForInterrupt:
+ // The interrupt handler may reenter the engine. In that case
+ // there are two complications:
+ //
+ // - The waiting thread is not actually waiting on the
+ // condition variable so we have to record that it
+ // should be woken when the interrupt handler returns.
+ // To that end, we flag the thread as interrupted around
+ // the interrupt and check state_ when the interrupt
+ // handler returns. A wake() call that reaches the
+ // runtime during the interrupt sets state_ to Woken.
+ //
+ // - It is in principle possible for wait() to be
+ // reentered on the same thread/runtime and waiting on the
+ // same location and to yet again be interrupted and enter
+ // the interrupt handler. In this case, it is important
+ // that when another agent wakes waiters, all waiters using
+ // the same runtime on the same location are woken in LIFO
+ // order; FIFO may be the required order, but FIFO would
+ // fail to wake up the innermost call. Interrupts are
+ // outside any spec anyway. Also, several such suspended
+ // waiters may be woken at a time.
+ //
+ // For the time being we disallow waiting from within code
+ // that runs from within an interrupt handler; this may
+ // occasionally (very rarely) be surprising but is
+ // expedient. Other solutions exist, see bug #1131943. The
+ // code that performs the check is above, at the head of
+ // this function.
+
+ state_ = WaitingInterrupted;
+ {
+ UnlockGuard<Mutex> unlock(locked);
+ retval = cx->runtime()->handleInterrupt(cx);
+ }
+ if (!retval)
+ goto finished;
+ if (state_ == Woken) {
+ *result = FutexOK;
+ goto finished;
+ }
+ break;
+
+ default:
+ MOZ_CRASH("Bad FutexState in wait()");
+ }
+ }
+finished:
+ state_ = Idle;
+ return retval;
+}
+
+void
+js::FutexRuntime::wake(WakeReason reason)
+{
+ MOZ_ASSERT(isWaiting());
+
+ if ((state_ == WaitingInterrupted || state_ == WaitingNotifiedForInterrupt) && reason == WakeExplicit) {
+ state_ = Woken;
+ return;
+ }
+ switch (reason) {
+ case WakeExplicit:
+ state_ = Woken;
+ break;
+ case WakeForJSInterrupt:
+ if (state_ == WaitingNotifiedForInterrupt)
+ return;
+ state_ = WaitingNotifiedForInterrupt;
+ break;
+ default:
+ MOZ_CRASH("bad WakeReason in FutexRuntime::wake()");
+ }
+ cond_->notify_all();
+}
+
+const JSFunctionSpec AtomicsMethods[] = {
+ JS_INLINABLE_FN("compareExchange", atomics_compareExchange, 4,0, AtomicsCompareExchange),
+ JS_INLINABLE_FN("load", atomics_load, 2,0, AtomicsLoad),
+ JS_INLINABLE_FN("store", atomics_store, 3,0, AtomicsStore),
+ JS_INLINABLE_FN("exchange", atomics_exchange, 3,0, AtomicsExchange),
+ JS_INLINABLE_FN("add", atomics_add, 3,0, AtomicsAdd),
+ JS_INLINABLE_FN("sub", atomics_sub, 3,0, AtomicsSub),
+ JS_INLINABLE_FN("and", atomics_and, 3,0, AtomicsAnd),
+ JS_INLINABLE_FN("or", atomics_or, 3,0, AtomicsOr),
+ JS_INLINABLE_FN("xor", atomics_xor, 3,0, AtomicsXor),
+ JS_INLINABLE_FN("isLockFree", atomics_isLockFree, 1,0, AtomicsIsLockFree),
+ JS_FN("wait", atomics_wait, 4,0),
+ JS_FN("wake", atomics_wake, 3,0),
+ JS_FS_END
+};
+
+JSObject*
+AtomicsObject::initClass(JSContext* cx, Handle<GlobalObject*> global)
+{
+ // Create Atomics Object.
+ RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
+ if (!objProto)
+ return nullptr;
+ RootedObject Atomics(cx, NewObjectWithGivenProto(cx, &AtomicsObject::class_, objProto,
+ SingletonObject));
+ if (!Atomics)
+ return nullptr;
+
+ if (!JS_DefineFunctions(cx, Atomics, AtomicsMethods))
+ return nullptr;
+
+ RootedValue AtomicsValue(cx, ObjectValue(*Atomics));
+
+ // Everything is set up, install Atomics on the global object.
+ if (!DefineProperty(cx, global, cx->names().Atomics, AtomicsValue, nullptr, nullptr,
+ JSPROP_RESOLVING))
+ {
+ return nullptr;
+ }
+
+ global->setConstructor(JSProto_Atomics, AtomicsValue);
+ return Atomics;
+}
+
+JSObject*
+js::InitAtomicsClass(JSContext* cx, HandleObject obj)
+{
+ MOZ_ASSERT(obj->is<GlobalObject>());
+ Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+ return AtomicsObject::initClass(cx, global);
+}
+
+#undef CXX11_ATOMICS
+#undef GNU_ATOMICS
diff --git a/js/src/builtin/AtomicsObject.h b/js/src/builtin/AtomicsObject.h
new file mode 100644
index 000000000..adb6fb986
--- /dev/null
+++ b/js/src/builtin/AtomicsObject.h
@@ -0,0 +1,156 @@
+/* -*- 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/. */
+
+#ifndef builtin_AtomicsObject_h
+#define builtin_AtomicsObject_h
+
+#include "mozilla/Maybe.h"
+#include "mozilla/TimeStamp.h"
+
+#include "jsobj.h"
+
+#include "threading/ConditionVariable.h"
+#include "vm/MutexIDs.h"
+
+namespace js {
+
+class AtomicsObject : public JSObject
+{
+ public:
+ static const Class class_;
+ static JSObject* initClass(JSContext* cx, Handle<GlobalObject*> global);
+ static MOZ_MUST_USE bool toString(JSContext* cx, unsigned int argc, Value* vp);
+};
+
+MOZ_MUST_USE bool atomics_compareExchange(JSContext* cx, unsigned argc, Value* vp);
+MOZ_MUST_USE bool atomics_exchange(JSContext* cx, unsigned argc, Value* vp);
+MOZ_MUST_USE bool atomics_load(JSContext* cx, unsigned argc, Value* vp);
+MOZ_MUST_USE bool atomics_store(JSContext* cx, unsigned argc, Value* vp);
+MOZ_MUST_USE bool atomics_add(JSContext* cx, unsigned argc, Value* vp);
+MOZ_MUST_USE bool atomics_sub(JSContext* cx, unsigned argc, Value* vp);
+MOZ_MUST_USE bool atomics_and(JSContext* cx, unsigned argc, Value* vp);
+MOZ_MUST_USE bool atomics_or(JSContext* cx, unsigned argc, Value* vp);
+MOZ_MUST_USE bool atomics_xor(JSContext* cx, unsigned argc, Value* vp);
+MOZ_MUST_USE bool atomics_isLockFree(JSContext* cx, unsigned argc, Value* vp);
+MOZ_MUST_USE bool atomics_wait(JSContext* cx, unsigned argc, Value* vp);
+MOZ_MUST_USE bool atomics_wake(JSContext* cx, unsigned argc, Value* vp);
+
+/* asm.js callouts */
+namespace wasm { class Instance; }
+int32_t atomics_add_asm_callout(wasm::Instance* i, int32_t vt, int32_t offset, int32_t value);
+int32_t atomics_sub_asm_callout(wasm::Instance* i, int32_t vt, int32_t offset, int32_t value);
+int32_t atomics_and_asm_callout(wasm::Instance* i, int32_t vt, int32_t offset, int32_t value);
+int32_t atomics_or_asm_callout(wasm::Instance* i, int32_t vt, int32_t offset, int32_t value);
+int32_t atomics_xor_asm_callout(wasm::Instance* i, int32_t vt, int32_t offset, int32_t value);
+int32_t atomics_cmpxchg_asm_callout(wasm::Instance* i, int32_t vt, int32_t offset, int32_t oldval, int32_t newval);
+int32_t atomics_xchg_asm_callout(wasm::Instance* i, int32_t vt, int32_t offset, int32_t value);
+
+class FutexRuntime
+{
+ friend class AutoLockFutexAPI;
+
+public:
+ static MOZ_MUST_USE bool initialize();
+ static void destroy();
+
+ static void lock();
+ static void unlock();
+
+ FutexRuntime();
+ MOZ_MUST_USE bool initInstance();
+ void destroyInstance();
+
+ // Parameters to wake().
+ enum WakeReason {
+ WakeExplicit, // Being asked to wake up by another thread
+ WakeForJSInterrupt // Interrupt requested
+ };
+
+ // Result code from wait().
+ enum WaitResult {
+ FutexOK,
+ FutexTimedOut
+ };
+
+ // Block the calling thread and wait.
+ //
+ // The futex lock must be held around this call.
+ //
+ // The timeout is the number of milliseconds, with fractional
+ // times allowed; specify mozilla::Nothing() for an indefinite
+ // wait.
+ //
+ // wait() will not wake up spuriously. It will return true and
+ // set *result to a return code appropriate for
+ // Atomics.wait() on success, and return false on error.
+ MOZ_MUST_USE bool wait(JSContext* cx, js::UniqueLock<js::Mutex>& locked,
+ mozilla::Maybe<mozilla::TimeDuration>& timeout, WaitResult* result);
+
+ // Wake the thread represented by this Runtime.
+ //
+ // The futex lock must be held around this call. (The sleeping
+ // thread will not wake up until the caller of Atomics.wake()
+ // releases the lock.)
+ //
+ // If the thread is not waiting then this method does nothing.
+ //
+ // If the thread is waiting in a call to wait() and the
+ // reason is WakeExplicit then the wait() call will return
+ // with Woken.
+ //
+ // If the thread is waiting in a call to wait() and the
+ // reason is WakeForJSInterrupt then the wait() will return
+ // with WaitingNotifiedForInterrupt; in the latter case the caller
+ // of wait() must handle the interrupt.
+ void wake(WakeReason reason);
+
+ bool isWaiting();
+
+ // If canWait() returns false (the default) then wait() is disabled
+ // on the runtime to which the FutexRuntime belongs.
+ bool canWait() {
+ return canWait_;
+ }
+
+ void setCanWait(bool flag) {
+ canWait_ = flag;
+ }
+
+ private:
+ enum FutexState {
+ Idle, // We are not waiting or woken
+ Waiting, // We are waiting, nothing has happened yet
+ WaitingNotifiedForInterrupt, // We are waiting, but have been interrupted,
+ // and have not yet started running the
+ // interrupt handler
+ WaitingInterrupted, // We are waiting, but have been interrupted
+ // and are running the interrupt handler
+ Woken // Woken by a script call to Atomics.wake
+ };
+
+ // Condition variable that this runtime will wait on.
+ js::ConditionVariable* cond_;
+
+ // Current futex state for this runtime. When not in a wait this
+ // is Idle; when in a wait it is Waiting or the reason the futex
+ // is about to wake up.
+ FutexState state_;
+
+ // Shared futex lock for all runtimes. We can perhaps do better,
+ // but any lock will need to be per-domain (consider SharedWorker)
+ // or coarser.
+ static mozilla::Atomic<js::Mutex*> lock_;
+
+ // A flag that controls whether waiting is allowed.
+ bool canWait_;
+};
+
+JSObject*
+InitAtomicsClass(JSContext* cx, HandleObject obj);
+
+} /* namespace js */
+
+#endif /* builtin_AtomicsObject_h */
diff --git a/js/src/builtin/Classes.js b/js/src/builtin/Classes.js
new file mode 100644
index 000000000..d0f20b5fd
--- /dev/null
+++ b/js/src/builtin/Classes.js
@@ -0,0 +1,17 @@
+// Give a builtin constructor that we can use as the default. When we give
+// it to our newly made class, we will be sure to set it up with the correct name
+// and .prototype, so that everything works properly.
+
+var DefaultDerivedClassConstructor =
+ class extends null {
+ constructor(...args) {
+ super(...allowContentSpread(args));
+ }
+ };
+MakeDefaultConstructor(DefaultDerivedClassConstructor);
+
+var DefaultBaseClassConstructor =
+ class {
+ constructor() { }
+ };
+MakeDefaultConstructor(DefaultBaseClassConstructor);
diff --git a/js/src/builtin/Date.js b/js/src/builtin/Date.js
new file mode 100644
index 000000000..6983d2685
--- /dev/null
+++ b/js/src/builtin/Date.js
@@ -0,0 +1,174 @@
+/* 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/. */
+
+/*global intl_DateTimeFormat: false, */
+
+
+// This cache, once primed, has these properties:
+//
+// runtimeDefaultLocale:
+// Locale information provided by the embedding, guiding SpiderMonkey's
+// selection of a default locale. See RuntimeDefaultLocale(), whose
+// value controls the value returned by DefaultLocale() that's what's
+// *actually* used.
+// icuDefaultTimeZone:
+// Time zone information provided by ICU. See intl_defaultTimeZone(),
+// whose value controls the value returned by DefaultTimeZone() that's
+// what's *actually* used.
+// formatters:
+// A Record storing formatters consistent with the above
+// runtimeDefaultLocale/localTZA values, for use with the appropriate
+// ES6 toLocale*String Date method when called with its first two
+// arguments having the value |undefined|.
+//
+// The "formatters" Record has (some subset of) these properties, as determined
+// by all values of the first argument passed to |GetCachedFormat|:
+//
+// dateTimeFormat: for Date's toLocaleString operation
+// dateFormat: for Date's toLocaleDateString operation
+// timeFormat: for Date's toLocaleTimeString operation
+//
+// Using this cache, then, requires
+// 1) verifying the current runtimeDefaultLocale/icuDefaultTimeZone are
+// consistent with cached values, then
+// 2) seeing if the desired formatter is cached and returning it if so, or else
+// 3) create the desired formatter and store and return it.
+var dateTimeFormatCache = new Record();
+
+
+/**
+ * Get a cached DateTimeFormat formatter object, created like so:
+ *
+ * var opts = ToDateTimeOptions(undefined, required, defaults);
+ * return new Intl.DateTimeFormat(undefined, opts);
+ *
+ * |format| must be a key from the "formatters" Record described above.
+ */
+function GetCachedFormat(format, required, defaults) {
+ assert(format === "dateTimeFormat" ||
+ format === "dateFormat" ||
+ format === "timeFormat",
+ "unexpected format key: please update the comment by " +
+ "dateTimeFormatCache");
+
+ var runtimeDefaultLocale = RuntimeDefaultLocale();
+ var icuDefaultTimeZone = intl_defaultTimeZone();
+
+ var formatters;
+ if (dateTimeFormatCache.runtimeDefaultLocale !== runtimeDefaultLocale ||
+ dateTimeFormatCache.icuDefaultTimeZone !== icuDefaultTimeZone)
+ {
+ formatters = dateTimeFormatCache.formatters = new Record();
+ dateTimeFormatCache.runtimeDefaultLocale = runtimeDefaultLocale;
+ dateTimeFormatCache.icuDefaultTimeZone = icuDefaultTimeZone;
+ } else {
+ formatters = dateTimeFormatCache.formatters;
+ }
+
+ var fmt = formatters[format];
+ if (fmt === undefined) {
+ var options = ToDateTimeOptions(undefined, required, defaults);
+ fmt = formatters[format] = intl_DateTimeFormat(undefined, options);
+ }
+
+ return fmt;
+}
+
+/**
+ * Format this Date object into a date and time string, using the locale and
+ * formatting options provided.
+ *
+ * Spec: ECMAScript Language Specification, 5.1 edition, 15.9.5.5.
+ * Spec: ECMAScript Internationalization API Specification, 13.3.1.
+ */
+function Date_toLocaleString() {
+ // Steps 1-2. Note that valueOf enforces "this time value" restrictions.
+ var x = callFunction(std_Date_valueOf, this);
+ if (Number_isNaN(x))
+ return "Invalid Date";
+
+ // Steps 3-4.
+ var locales = arguments.length > 0 ? arguments[0] : undefined;
+ var options = arguments.length > 1 ? arguments[1] : undefined;
+
+ // Step 5-6.
+ var dateTimeFormat;
+ if (locales === undefined && options === undefined) {
+ // This cache only optimizes for the old ES5 toLocaleString without
+ // locales and options.
+ dateTimeFormat = GetCachedFormat("dateTimeFormat", "any", "all");
+ } else {
+ options = ToDateTimeOptions(options, "any", "all");
+ dateTimeFormat = intl_DateTimeFormat(locales, options);
+ }
+
+ // Step 7.
+ return intl_FormatDateTime(dateTimeFormat, x, false);
+}
+
+
+/**
+ * Format this Date object into a date string, using the locale and formatting
+ * options provided.
+ *
+ * Spec: ECMAScript Language Specification, 5.1 edition, 15.9.5.6.
+ * Spec: ECMAScript Internationalization API Specification, 13.3.2.
+ */
+function Date_toLocaleDateString() {
+ // Steps 1-2. Note that valueOf enforces "this time value" restrictions.
+ var x = callFunction(std_Date_valueOf, this);
+ if (Number_isNaN(x))
+ return "Invalid Date";
+
+ // Steps 3-4.
+ var locales = arguments.length > 0 ? arguments[0] : undefined;
+ var options = arguments.length > 1 ? arguments[1] : undefined;
+
+ // Step 5-6.
+ var dateTimeFormat;
+ if (locales === undefined && options === undefined) {
+ // This cache only optimizes for the old ES5 toLocaleDateString without
+ // locales and options.
+ dateTimeFormat = GetCachedFormat("dateFormat", "date", "date");
+ } else {
+ options = ToDateTimeOptions(options, "date", "date");
+ dateTimeFormat = intl_DateTimeFormat(locales, options);
+ }
+
+ // Step 7.
+ return intl_FormatDateTime(dateTimeFormat, x, false);
+}
+
+
+/**
+ * Format this Date object into a time string, using the locale and formatting
+ * options provided.
+ *
+ * Spec: ECMAScript Language Specification, 5.1 edition, 15.9.5.7.
+ * Spec: ECMAScript Internationalization API Specification, 13.3.3.
+ */
+function Date_toLocaleTimeString() {
+ // Steps 1-2. Note that valueOf enforces "this time value" restrictions.
+ var x = callFunction(std_Date_valueOf, this);
+ if (Number_isNaN(x))
+ return "Invalid Date";
+
+ // Steps 3-4.
+ var locales = arguments.length > 0 ? arguments[0] : undefined;
+ var options = arguments.length > 1 ? arguments[1] : undefined;
+
+ // Step 5-6.
+ var dateTimeFormat;
+ if (locales === undefined && options === undefined) {
+ // This cache only optimizes for the old ES5 toLocaleTimeString without
+ // locales and options.
+ dateTimeFormat = GetCachedFormat("timeFormat", "time", "time");
+ } else {
+ options = ToDateTimeOptions(options, "time", "time");
+ dateTimeFormat = intl_DateTimeFormat(locales, options);
+ }
+
+ // Step 7.
+ return intl_FormatDateTime(dateTimeFormat, x, false);
+}
diff --git a/js/src/builtin/Error.js b/js/src/builtin/Error.js
new file mode 100644
index 000000000..b0fd727eb
--- /dev/null
+++ b/js/src/builtin/Error.js
@@ -0,0 +1,36 @@
+/* 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/. */
+
+/* ES6 20140718 draft 19.5.3.4. */
+function ErrorToString()
+{
+ /* Steps 1-2. */
+ var obj = this;
+ if (!IsObject(obj))
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "Error", "toString", "value");
+
+ /* Steps 3-5. */
+ var name = obj.name;
+ name = (name === undefined) ? "Error" : ToString(name);
+
+ /* Steps 6-8. */
+ var msg = obj.message;
+ msg = (msg === undefined) ? "" : ToString(msg);
+
+ /* Step 9. */
+ if (name === "")
+ return msg;
+
+ /* Step 10. */
+ if (msg === "")
+ return name;
+
+ /* Step 11. */
+ return name + ": " + msg;
+}
+
+function ErrorToStringWithTrailingNewline()
+{
+ return FUN_APPLY(ErrorToString, this, []) + "\n";
+}
diff --git a/js/src/builtin/Eval.cpp b/js/src/builtin/Eval.cpp
new file mode 100644
index 000000000..87321ba04
--- /dev/null
+++ b/js/src/builtin/Eval.cpp
@@ -0,0 +1,485 @@
+/* -*- 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 "builtin/Eval.h"
+
+#include "mozilla/HashFunctions.h"
+#include "mozilla/Range.h"
+
+#include "jscntxt.h"
+#include "jshashutil.h"
+
+#include "frontend/BytecodeCompiler.h"
+#include "vm/Debugger.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSONParser.h"
+
+#include "vm/Interpreter-inl.h"
+
+using namespace js;
+
+using mozilla::AddToHash;
+using mozilla::HashString;
+using mozilla::RangedPtr;
+
+using JS::AutoCheckCannotGC;
+
+// We should be able to assert this for *any* fp->environmentChain().
+static void
+AssertInnerizedEnvironmentChain(JSContext* cx, JSObject& env)
+{
+#ifdef DEBUG
+ RootedObject obj(cx);
+ for (obj = &env; obj; obj = obj->enclosingEnvironment())
+ MOZ_ASSERT(!IsWindowProxy(obj));
+#endif
+}
+
+static bool
+IsEvalCacheCandidate(JSScript* script)
+{
+ // Make sure there are no inner objects which might use the wrong parent
+ // and/or call scope by reusing the previous eval's script.
+ return script->isDirectEvalInFunction() &&
+ !script->hasSingletons() &&
+ !script->hasObjects();
+}
+
+/* static */ HashNumber
+EvalCacheHashPolicy::hash(const EvalCacheLookup& l)
+{
+ AutoCheckCannotGC nogc;
+ uint32_t hash = l.str->hasLatin1Chars()
+ ? HashString(l.str->latin1Chars(nogc), l.str->length())
+ : HashString(l.str->twoByteChars(nogc), l.str->length());
+ return AddToHash(hash, l.callerScript.get(), l.version, l.pc);
+}
+
+/* static */ bool
+EvalCacheHashPolicy::match(const EvalCacheEntry& cacheEntry, const EvalCacheLookup& l)
+{
+ JSScript* script = cacheEntry.script;
+
+ MOZ_ASSERT(IsEvalCacheCandidate(script));
+
+ return EqualStrings(cacheEntry.str, l.str) &&
+ cacheEntry.callerScript == l.callerScript &&
+ script->getVersion() == l.version &&
+ cacheEntry.pc == l.pc;
+}
+
+// Add the script to the eval cache when EvalKernel is finished
+class EvalScriptGuard
+{
+ JSContext* cx_;
+ Rooted<JSScript*> script_;
+
+ /* These fields are only valid if lookup_.str is non-nullptr. */
+ EvalCacheLookup lookup_;
+ mozilla::Maybe<DependentAddPtr<EvalCache>> p_;
+
+ RootedLinearString lookupStr_;
+
+ public:
+ explicit EvalScriptGuard(JSContext* cx)
+ : cx_(cx), script_(cx), lookup_(cx), lookupStr_(cx) {}
+
+ ~EvalScriptGuard() {
+ if (script_ && !cx_->isExceptionPending()) {
+ script_->cacheForEval();
+ EvalCacheEntry cacheEntry = {lookupStr_, script_, lookup_.callerScript, lookup_.pc};
+ lookup_.str = lookupStr_;
+ if (lookup_.str && IsEvalCacheCandidate(script_)) {
+ // Ignore failure to add cache entry.
+ if (!p_->add(cx_, cx_->caches.evalCache, lookup_, cacheEntry))
+ cx_->recoverFromOutOfMemory();
+ }
+ }
+ }
+
+ void lookupInEvalCache(JSLinearString* str, JSScript* callerScript, jsbytecode* pc)
+ {
+ lookupStr_ = str;
+ lookup_.str = str;
+ lookup_.callerScript = callerScript;
+ lookup_.version = cx_->findVersion();
+ lookup_.pc = pc;
+ p_.emplace(cx_, cx_->caches.evalCache, lookup_);
+ if (*p_) {
+ script_ = (*p_)->script;
+ p_->remove(cx_, cx_->caches.evalCache, lookup_);
+ script_->uncacheForEval();
+ }
+ }
+
+ void setNewScript(JSScript* script) {
+ // JSScript::initFromEmitter has already called js_CallNewScriptHook.
+ MOZ_ASSERT(!script_ && script);
+ script_ = script;
+ script_->setActiveEval();
+ }
+
+ bool foundScript() {
+ return !!script_;
+ }
+
+ HandleScript script() {
+ MOZ_ASSERT(script_);
+ return script_;
+ }
+};
+
+enum EvalJSONResult {
+ EvalJSON_Failure,
+ EvalJSON_Success,
+ EvalJSON_NotJSON
+};
+
+template <typename CharT>
+static bool
+EvalStringMightBeJSON(const mozilla::Range<const CharT> chars)
+{
+ // If the eval string starts with '(' or '[' and ends with ')' or ']', it may be JSON.
+ // Try the JSON parser first because it's much faster. If the eval string
+ // isn't JSON, JSON parsing will probably fail quickly, so little time
+ // will be lost.
+ size_t length = chars.length();
+ if (length > 2 &&
+ ((chars[0] == '[' && chars[length - 1] == ']') ||
+ (chars[0] == '(' && chars[length - 1] == ')')))
+ {
+ // Remarkably, JavaScript syntax is not a superset of JSON syntax:
+ // strings in JavaScript cannot contain the Unicode line and paragraph
+ // terminator characters U+2028 and U+2029, but strings in JSON can.
+ // Rather than force the JSON parser to handle this quirk when used by
+ // eval, we simply don't use the JSON parser when either character
+ // appears in the provided string. See bug 657367.
+ if (sizeof(CharT) > 1) {
+ for (RangedPtr<const CharT> cp = chars.begin() + 1, end = chars.end() - 1;
+ cp < end;
+ cp++)
+ {
+ char16_t c = *cp;
+ if (c == 0x2028 || c == 0x2029)
+ return false;
+ }
+ }
+
+ return true;
+ }
+ return false;
+}
+
+template <typename CharT>
+static EvalJSONResult
+ParseEvalStringAsJSON(JSContext* cx, const mozilla::Range<const CharT> chars, MutableHandleValue rval)
+{
+ size_t len = chars.length();
+ MOZ_ASSERT((chars[0] == '(' && chars[len - 1] == ')') ||
+ (chars[0] == '[' && chars[len - 1] == ']'));
+
+ auto jsonChars = (chars[0] == '[')
+ ? chars
+ : mozilla::Range<const CharT>(chars.begin().get() + 1U, len - 2);
+
+ Rooted<JSONParser<CharT>> parser(cx, JSONParser<CharT>(cx, jsonChars, JSONParserBase::NoError));
+ if (!parser.parse(rval))
+ return EvalJSON_Failure;
+
+ return rval.isUndefined() ? EvalJSON_NotJSON : EvalJSON_Success;
+}
+
+static EvalJSONResult
+TryEvalJSON(JSContext* cx, JSLinearString* str, MutableHandleValue rval)
+{
+ if (str->hasLatin1Chars()) {
+ AutoCheckCannotGC nogc;
+ if (!EvalStringMightBeJSON(str->latin1Range(nogc)))
+ return EvalJSON_NotJSON;
+ } else {
+ AutoCheckCannotGC nogc;
+ if (!EvalStringMightBeJSON(str->twoByteRange(nogc)))
+ return EvalJSON_NotJSON;
+ }
+
+ AutoStableStringChars linearChars(cx);
+ if (!linearChars.init(cx, str))
+ return EvalJSON_Failure;
+
+ return linearChars.isLatin1()
+ ? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval)
+ : ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval);
+}
+
+enum EvalType { DIRECT_EVAL, INDIRECT_EVAL };
+
+// Common code implementing direct and indirect eval.
+//
+// Evaluate call.argv[2], if it is a string, in the context of the given calling
+// frame, with the provided scope chain, with the semantics of either a direct
+// or indirect eval (see ES5 10.4.2). If this is an indirect eval, env
+// must be a global object.
+//
+// On success, store the completion value in call.rval and return true.
+static bool
+EvalKernel(JSContext* cx, HandleValue v, EvalType evalType, AbstractFramePtr caller,
+ HandleObject env, jsbytecode* pc, MutableHandleValue vp)
+{
+ MOZ_ASSERT((evalType == INDIRECT_EVAL) == !caller);
+ MOZ_ASSERT((evalType == INDIRECT_EVAL) == !pc);
+ MOZ_ASSERT_IF(evalType == INDIRECT_EVAL, IsGlobalLexicalEnvironment(env));
+ AssertInnerizedEnvironmentChain(cx, *env);
+
+ Rooted<GlobalObject*> envGlobal(cx, &env->global());
+ if (!GlobalObject::isRuntimeCodeGenEnabled(cx, envGlobal)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
+ return false;
+ }
+
+ // ES5 15.1.2.1 step 1.
+ if (!v.isString()) {
+ vp.set(v);
+ return true;
+ }
+ RootedString str(cx, v.toString());
+
+ // ES5 15.1.2.1 steps 2-8.
+
+ // Per ES5, indirect eval runs in the global scope. (eval is specified this
+ // way so that the compiler can make assumptions about what bindings may or
+ // may not exist in the current frame if it doesn't see 'eval'.)
+ MOZ_ASSERT_IF(evalType != DIRECT_EVAL,
+ cx->global() == &env->as<LexicalEnvironmentObject>().global());
+
+ RootedLinearString linearStr(cx, str->ensureLinear(cx));
+ if (!linearStr)
+ return false;
+
+ RootedScript callerScript(cx, caller ? caller.script() : nullptr);
+ EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
+ if (ejr != EvalJSON_NotJSON)
+ return ejr == EvalJSON_Success;
+
+ EvalScriptGuard esg(cx);
+
+ if (evalType == DIRECT_EVAL && caller.isFunctionFrame())
+ esg.lookupInEvalCache(linearStr, callerScript, pc);
+
+ if (!esg.foundScript()) {
+ RootedScript maybeScript(cx);
+ unsigned lineno;
+ const char* filename;
+ bool mutedErrors;
+ uint32_t pcOffset;
+ DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
+ &mutedErrors,
+ evalType == DIRECT_EVAL
+ ? CALLED_FROM_JSOP_EVAL
+ : NOT_CALLED_FROM_JSOP_EVAL);
+
+ const char* introducerFilename = filename;
+ if (maybeScript && maybeScript->scriptSource()->introducerFilename())
+ introducerFilename = maybeScript->scriptSource()->introducerFilename();
+
+ RootedScope enclosing(cx);
+ if (evalType == DIRECT_EVAL)
+ enclosing = callerScript->innermostScope(pc);
+ else
+ enclosing = &cx->global()->emptyGlobalScope();
+
+ CompileOptions options(cx);
+ options.setIsRunOnce(true)
+ .setNoScriptRval(false)
+ .setMutedErrors(mutedErrors)
+ .maybeMakeStrictMode(evalType == DIRECT_EVAL && IsStrictEvalPC(pc));
+
+ if (introducerFilename) {
+ options.setFileAndLine(filename, 1);
+ options.setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset);
+ } else {
+ options.setFileAndLine("eval", 1);
+ options.setIntroductionType("eval");
+ }
+
+ AutoStableStringChars linearChars(cx);
+ if (!linearChars.initTwoByte(cx, linearStr))
+ return false;
+
+ const char16_t* chars = linearChars.twoByteRange().begin().get();
+ SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
+ ? SourceBufferHolder::GiveOwnership
+ : SourceBufferHolder::NoOwnership;
+ SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
+ JSScript* compiled = frontend::CompileEvalScript(cx, cx->tempLifoAlloc(),
+ env, enclosing,
+ options, srcBuf);
+ if (!compiled)
+ return false;
+
+ esg.setNewScript(compiled);
+ }
+
+ // Look up the newTarget from the frame iterator.
+ Value newTargetVal = NullValue();
+ return ExecuteKernel(cx, esg.script(), *env, newTargetVal,
+ NullFramePtr() /* evalInFrame */, vp.address());
+}
+
+bool
+js::DirectEvalStringFromIon(JSContext* cx,
+ HandleObject env, HandleScript callerScript,
+ HandleValue newTargetValue, HandleString str,
+ jsbytecode* pc, MutableHandleValue vp)
+{
+ AssertInnerizedEnvironmentChain(cx, *env);
+
+ Rooted<GlobalObject*> envGlobal(cx, &env->global());
+ if (!GlobalObject::isRuntimeCodeGenEnabled(cx, envGlobal)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
+ return false;
+ }
+
+ // ES5 15.1.2.1 steps 2-8.
+
+ RootedLinearString linearStr(cx, str->ensureLinear(cx));
+ if (!linearStr)
+ return false;
+
+ EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
+ if (ejr != EvalJSON_NotJSON)
+ return ejr == EvalJSON_Success;
+
+ EvalScriptGuard esg(cx);
+
+ esg.lookupInEvalCache(linearStr, callerScript, pc);
+
+ if (!esg.foundScript()) {
+ RootedScript maybeScript(cx);
+ const char* filename;
+ unsigned lineno;
+ bool mutedErrors;
+ uint32_t pcOffset;
+ DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
+ &mutedErrors, CALLED_FROM_JSOP_EVAL);
+
+ const char* introducerFilename = filename;
+ if (maybeScript && maybeScript->scriptSource()->introducerFilename())
+ introducerFilename = maybeScript->scriptSource()->introducerFilename();
+
+ RootedScope enclosing(cx, callerScript->innermostScope(pc));
+
+ CompileOptions options(cx);
+ options.setIsRunOnce(true)
+ .setNoScriptRval(false)
+ .setMutedErrors(mutedErrors)
+ .maybeMakeStrictMode(IsStrictEvalPC(pc));
+
+ if (introducerFilename) {
+ options.setFileAndLine(filename, 1);
+ options.setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset);
+ } else {
+ options.setFileAndLine("eval", 1);
+ options.setIntroductionType("eval");
+ }
+
+ AutoStableStringChars linearChars(cx);
+ if (!linearChars.initTwoByte(cx, linearStr))
+ return false;
+
+ const char16_t* chars = linearChars.twoByteRange().begin().get();
+ SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
+ ? SourceBufferHolder::GiveOwnership
+ : SourceBufferHolder::NoOwnership;
+ SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
+ JSScript* compiled = frontend::CompileEvalScript(cx, cx->tempLifoAlloc(),
+ env, enclosing,
+ options, srcBuf);
+ if (!compiled)
+ return false;
+
+ esg.setNewScript(compiled);
+ }
+
+ return ExecuteKernel(cx, esg.script(), *env, newTargetValue,
+ NullFramePtr() /* evalInFrame */, vp.address());
+}
+
+bool
+js::IndirectEval(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<GlobalObject*> global(cx, &args.callee().global());
+ RootedObject globalLexical(cx, &global->lexicalEnvironment());
+
+ // Note we'll just pass |undefined| here, then return it directly (or throw
+ // if runtime codegen is disabled), if no argument is provided.
+ return EvalKernel(cx, args.get(0), INDIRECT_EVAL, NullFramePtr(), globalLexical, nullptr,
+ args.rval());
+}
+
+bool
+js::DirectEval(JSContext* cx, HandleValue v, MutableHandleValue vp)
+{
+ // Direct eval can assume it was called from an interpreted or baseline frame.
+ ScriptFrameIter iter(cx);
+ AbstractFramePtr caller = iter.abstractFramePtr();
+
+ MOZ_ASSERT(JSOp(*iter.pc()) == JSOP_EVAL ||
+ JSOp(*iter.pc()) == JSOP_STRICTEVAL ||
+ JSOp(*iter.pc()) == JSOP_SPREADEVAL ||
+ JSOp(*iter.pc()) == JSOP_STRICTSPREADEVAL);
+ MOZ_ASSERT(caller.compartment() == caller.script()->compartment());
+
+ RootedObject envChain(cx, caller.environmentChain());
+ return EvalKernel(cx, v, DIRECT_EVAL, caller, envChain, iter.pc(), vp);
+}
+
+bool
+js::IsAnyBuiltinEval(JSFunction* fun)
+{
+ return fun->maybeNative() == IndirectEval;
+}
+
+JS_FRIEND_API(bool)
+js::ExecuteInGlobalAndReturnScope(JSContext* cx, HandleObject global, HandleScript scriptArg,
+ MutableHandleObject envArg)
+{
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, global);
+ MOZ_ASSERT(global->is<GlobalObject>());
+ MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
+
+ RootedScript script(cx, scriptArg);
+ Rooted<GlobalObject*> globalRoot(cx, &global->as<GlobalObject>());
+ if (script->compartment() != cx->compartment()) {
+ script = CloneGlobalScript(cx, ScopeKind::NonSyntactic, script);
+ if (!script)
+ return false;
+
+ Debugger::onNewScript(cx, script);
+ }
+
+ Rooted<EnvironmentObject*> env(cx, NonSyntacticVariablesObject::create(cx));
+ if (!env)
+ return false;
+
+ // Unlike the non-syntactic scope chain API used by the subscript loader,
+ // this API creates a fresh block scope each time.
+ env = LexicalEnvironmentObject::createNonSyntactic(cx, env);
+ if (!env)
+ return false;
+
+ RootedValue rval(cx);
+ if (!ExecuteKernel(cx, script, *env, UndefinedValue(),
+ NullFramePtr() /* evalInFrame */, rval.address()))
+ {
+ return false;
+ }
+
+ envArg.set(env);
+ return true;
+}
diff --git a/js/src/builtin/Eval.h b/js/src/builtin/Eval.h
new file mode 100644
index 000000000..03a75d599
--- /dev/null
+++ b/js/src/builtin/Eval.h
@@ -0,0 +1,42 @@
+/* -*- 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/. */
+
+#ifndef builtin_Eval_h
+#define builtin_Eval_h
+
+#include "jsbytecode.h"
+#include "NamespaceImports.h"
+
+namespace js {
+
+// The C++ native for 'eval' (ES5 15.1.2.1). The function is named "indirect
+// eval" because "direct eval" calls (as defined by the spec) will emit
+// JSOP_EVAL which in turn calls DirectEval. Thus, even though IndirectEval is
+// the callee function object for *all* calls to eval, it is by construction
+// only ever called in the case indirect eval.
+extern MOZ_MUST_USE bool
+IndirectEval(JSContext* cx, unsigned argc, Value* vp);
+
+// Performs a direct eval of |v| (a string containing code, or another value
+// that will be vacuously returned), which must correspond to the currently-
+// executing stack frame, which must be a script frame.
+extern MOZ_MUST_USE bool
+DirectEval(JSContext* cx, HandleValue v, MutableHandleValue vp);
+
+// Performs a direct eval called from Ion code.
+extern MOZ_MUST_USE bool
+DirectEvalStringFromIon(JSContext* cx,
+ HandleObject scopeObj, HandleScript callerScript,
+ HandleValue newTargetValue, HandleString str,
+ jsbytecode* pc, MutableHandleValue vp);
+
+// True iff fun is a built-in eval function.
+extern bool
+IsAnyBuiltinEval(JSFunction* fun);
+
+} // namespace js
+
+#endif /* builtin_Eval_h */
diff --git a/js/src/builtin/Function.js b/js/src/builtin/Function.js
new file mode 100644
index 000000000..1b38ede56
--- /dev/null
+++ b/js/src/builtin/Function.js
@@ -0,0 +1,284 @@
+/* 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/. */
+
+// ES7 draft (January 21, 2016) 19.2.3.2 Function.prototype.bind
+function FunctionBind(thisArg, ...boundArgs) {
+ // Step 1.
+ var target = this;
+ // Step 2.
+ if (!IsCallable(target))
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, 'Function', 'bind', target);
+
+ // Step 3 (implicit).
+ // Step 4.
+ var F;
+ var argCount = boundArgs.length;
+ switch (argCount) {
+ case 0:
+ F = bind_bindFunction0(target, thisArg, boundArgs);
+ break;
+ case 1:
+ F = bind_bindFunction1(target, thisArg, boundArgs);
+ break;
+ case 2:
+ F = bind_bindFunction2(target, thisArg, boundArgs);
+ break;
+ default:
+ F = bind_bindFunctionN(target, thisArg, boundArgs);
+ }
+
+ // Steps 5-11.
+ _FinishBoundFunctionInit(F, target, argCount);
+
+ // Ensure that the apply intrinsic has been cloned so it can be baked into
+ // JIT code.
+ var funApply = std_Function_apply;
+
+ // Step 12.
+ return F;
+}
+/**
+ * bind_bindFunction{0,1,2} are special cases of the generic bind_bindFunctionN
+ * below. They avoid the need to merge the lists of bound arguments and call
+ * arguments to the bound function into a new list which is then used in a
+ * destructuring call of the bound function.
+ *
+ * All three of these functions again have special-cases for call argument
+ * counts between 0 and 5. For calls with 6+ arguments, all - bound and call -
+ * arguments are copied into an array before invoking the generic call and
+ * construct helper functions. This avoids having to use rest parameters and
+ * destructuring in the fast path.
+ *
+ * All bind_bindFunction{X} functions have the same signature to enable simple
+ * reading out of closed-over state by debugging functions.
+ */
+function bind_bindFunction0(fun, thisArg, boundArgs) {
+ return function bound() {
+ var newTarget;
+ if (_IsConstructing()) {
+ newTarget = new.target;
+ if (newTarget === bound)
+ newTarget = fun;
+ switch (arguments.length) {
+ case 0:
+ return constructContentFunction(fun, newTarget);
+ case 1:
+ return constructContentFunction(fun, newTarget, SPREAD(arguments, 1));
+ case 2:
+ return constructContentFunction(fun, newTarget, SPREAD(arguments, 2));
+ case 3:
+ return constructContentFunction(fun, newTarget, SPREAD(arguments, 3));
+ case 4:
+ return constructContentFunction(fun, newTarget, SPREAD(arguments, 4));
+ case 5:
+ return constructContentFunction(fun, newTarget, SPREAD(arguments, 5));
+ }
+ } else {
+ switch (arguments.length) {
+ case 0:
+ return callContentFunction(fun, thisArg);
+ case 1:
+ return callContentFunction(fun, thisArg, SPREAD(arguments, 1));
+ case 2:
+ return callContentFunction(fun, thisArg, SPREAD(arguments, 2));
+ case 3:
+ return callContentFunction(fun, thisArg, SPREAD(arguments, 3));
+ case 4:
+ return callContentFunction(fun, thisArg, SPREAD(arguments, 4));
+ case 5:
+ return callContentFunction(fun, thisArg, SPREAD(arguments, 5));
+ }
+ }
+ var callArgs = FUN_APPLY(bind_mapArguments, null, arguments);
+ return bind_invokeFunctionN(fun, thisArg, newTarget, boundArgs, callArgs);
+ };
+}
+
+function bind_bindFunction1(fun, thisArg, boundArgs) {
+ var bound1 = boundArgs[0];
+ return function bound() {
+ var newTarget;
+ if (_IsConstructing()) {
+ newTarget = new.target;
+ if (newTarget === bound)
+ newTarget = fun;
+ switch (arguments.length) {
+ case 0:
+ return constructContentFunction(fun, newTarget, bound1);
+ case 1:
+ return constructContentFunction(fun, newTarget, bound1, SPREAD(arguments, 1));
+ case 2:
+ return constructContentFunction(fun, newTarget, bound1, SPREAD(arguments, 2));
+ case 3:
+ return constructContentFunction(fun, newTarget, bound1, SPREAD(arguments, 3));
+ case 4:
+ return constructContentFunction(fun, newTarget, bound1, SPREAD(arguments, 4));
+ case 5:
+ return constructContentFunction(fun, newTarget, bound1, SPREAD(arguments, 5));
+ }
+ } else {
+ switch (arguments.length) {
+ case 0:
+ return callContentFunction(fun, thisArg, bound1);
+ case 1:
+ return callContentFunction(fun, thisArg, bound1, SPREAD(arguments, 1));
+ case 2:
+ return callContentFunction(fun, thisArg, bound1, SPREAD(arguments, 2));
+ case 3:
+ return callContentFunction(fun, thisArg, bound1, SPREAD(arguments, 3));
+ case 4:
+ return callContentFunction(fun, thisArg, bound1, SPREAD(arguments, 4));
+ case 5:
+ return callContentFunction(fun, thisArg, bound1, SPREAD(arguments, 5));
+ }
+ }
+ var callArgs = FUN_APPLY(bind_mapArguments, null, arguments);
+ return bind_invokeFunctionN(fun, thisArg, newTarget, boundArgs, callArgs);
+ };
+}
+
+function bind_bindFunction2(fun, thisArg, boundArgs) {
+ var bound1 = boundArgs[0];
+ var bound2 = boundArgs[1];
+ return function bound() {
+ var newTarget;
+ if (_IsConstructing()) {
+ newTarget = new.target;
+ if (newTarget === bound)
+ newTarget = fun;
+ switch (arguments.length) {
+ case 0:
+ return constructContentFunction(fun, newTarget, bound1, bound2);
+ case 1:
+ return constructContentFunction(fun, newTarget, bound1, bound2, SPREAD(arguments, 1));
+ case 2:
+ return constructContentFunction(fun, newTarget, bound1, bound2, SPREAD(arguments, 2));
+ case 3:
+ return constructContentFunction(fun, newTarget, bound1, bound2, SPREAD(arguments, 3));
+ case 4:
+ return constructContentFunction(fun, newTarget, bound1, bound2, SPREAD(arguments, 4));
+ case 5:
+ return constructContentFunction(fun, newTarget, bound1, bound2, SPREAD(arguments, 5));
+ }
+ } else {
+ switch (arguments.length) {
+ case 0:
+ return callContentFunction(fun, thisArg, bound1, bound2);
+ case 1:
+ return callContentFunction(fun, thisArg, bound1, bound2, SPREAD(arguments, 1));
+ case 2:
+ return callContentFunction(fun, thisArg, bound1, bound2, SPREAD(arguments, 2));
+ case 3:
+ return callContentFunction(fun, thisArg, bound1, bound2, SPREAD(arguments, 3));
+ case 4:
+ return callContentFunction(fun, thisArg, bound1, bound2, SPREAD(arguments, 4));
+ case 5:
+ return callContentFunction(fun, thisArg, bound1, bound2, SPREAD(arguments, 5));
+ }
+ }
+ var callArgs = FUN_APPLY(bind_mapArguments, null, arguments);
+ return bind_invokeFunctionN(fun, thisArg, newTarget, boundArgs, callArgs);
+ };
+}
+
+function bind_bindFunctionN(fun, thisArg, boundArgs) {
+ assert(boundArgs.length > 2, "Fast paths should be used for few-bound-args cases.");
+ return function bound() {
+ var newTarget;
+ if (_IsConstructing()) {
+ newTarget = new.target;
+ if (newTarget === bound)
+ newTarget = fun;
+ }
+ if (arguments.length === 0) {
+ if (newTarget !== undefined)
+ return bind_constructFunctionN(fun, newTarget, boundArgs);
+ else
+ return bind_applyFunctionN(fun, thisArg, boundArgs);
+ }
+ var callArgs = FUN_APPLY(bind_mapArguments, null, arguments);
+ return bind_invokeFunctionN(fun, thisArg, newTarget, boundArgs, callArgs);
+ };
+}
+
+function bind_mapArguments() {
+ var len = arguments.length;
+ var args = std_Array(len);
+ for (var i = 0; i < len; i++)
+ _DefineDataProperty(args, i, arguments[i]);
+ return args;
+}
+
+function bind_invokeFunctionN(fun, thisArg, newTarget, boundArgs, callArgs) {
+ var boundArgsCount = boundArgs.length;
+ var callArgsCount = callArgs.length;
+ var args = std_Array(boundArgsCount + callArgsCount);
+ for (var i = 0; i < boundArgsCount; i++)
+ _DefineDataProperty(args, i, boundArgs[i]);
+ for (var i = 0; i < callArgsCount; i++)
+ _DefineDataProperty(args, i + boundArgsCount, callArgs[i]);
+ if (newTarget !== undefined)
+ return bind_constructFunctionN(fun, newTarget, args);
+ return bind_applyFunctionN(fun, thisArg, args);
+}
+
+function bind_applyFunctionN(fun, thisArg, args) {
+ switch (args.length) {
+ case 0:
+ return callContentFunction(fun, thisArg);
+ case 1:
+ return callContentFunction(fun, thisArg, SPREAD(args, 1));
+ case 2:
+ return callContentFunction(fun, thisArg, SPREAD(args, 2));
+ case 3:
+ return callContentFunction(fun, thisArg, SPREAD(args, 3));
+ case 4:
+ return callContentFunction(fun, thisArg, SPREAD(args, 4));
+ case 5:
+ return callContentFunction(fun, thisArg, SPREAD(args, 5));
+ case 6:
+ return callContentFunction(fun, thisArg, SPREAD(args, 6));
+ case 7:
+ return callContentFunction(fun, thisArg, SPREAD(args, 7));
+ case 8:
+ return callContentFunction(fun, thisArg, SPREAD(args, 8));
+ case 9:
+ return callContentFunction(fun, thisArg, SPREAD(args, 9));
+ default:
+ return FUN_APPLY(fun, thisArg, args);
+ }
+}
+
+function bind_constructFunctionN(fun, newTarget, args) {
+ switch (args.length) {
+ case 1:
+ return constructContentFunction(fun, newTarget, SPREAD(args, 1));
+ case 2:
+ return constructContentFunction(fun, newTarget, SPREAD(args, 2));
+ case 3:
+ return constructContentFunction(fun, newTarget, SPREAD(args, 3));
+ case 4:
+ return constructContentFunction(fun, newTarget, SPREAD(args, 4));
+ case 5:
+ return constructContentFunction(fun, newTarget, SPREAD(args, 5));
+ case 6:
+ return constructContentFunction(fun, newTarget, SPREAD(args, 6));
+ case 7:
+ return constructContentFunction(fun, newTarget, SPREAD(args, 7));
+ case 8:
+ return constructContentFunction(fun, newTarget, SPREAD(args, 8));
+ case 9:
+ return constructContentFunction(fun, newTarget, SPREAD(args, 9));
+ case 10:
+ return constructContentFunction(fun, newTarget, SPREAD(args, 10));
+ case 11:
+ return constructContentFunction(fun, newTarget, SPREAD(args, 11));
+ case 12:
+ return constructContentFunction(fun, newTarget, SPREAD(args, 12));
+ default:
+ assert(args.length !== 0,
+ "bound function construction without args should be handled by caller");
+ return _ConstructFunction(fun, newTarget, args);
+ }
+}
diff --git a/js/src/builtin/Generator.js b/js/src/builtin/Generator.js
new file mode 100644
index 000000000..bd1549660
--- /dev/null
+++ b/js/src/builtin/Generator.js
@@ -0,0 +1,148 @@
+/* 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/. */
+
+function StarGeneratorNext(val) {
+ // The IsSuspendedStarGenerator call below is not necessary for
+ // correctness. It's a performance optimization to check for the
+ // common case with a single call. It's also inlined in Baseline.
+
+ if (!IsSuspendedStarGenerator(this)) {
+ if (!IsObject(this) || !IsStarGeneratorObject(this))
+ return callFunction(CallStarGeneratorMethodIfWrapped, this, val, "StarGeneratorNext");
+
+ if (StarGeneratorObjectIsClosed(this))
+ return { value: undefined, done: true };
+
+ if (GeneratorIsRunning(this))
+ ThrowTypeError(JSMSG_NESTING_GENERATOR);
+ }
+
+ try {
+ return resumeGenerator(this, val, 'next');
+ } catch (e) {
+ if (!StarGeneratorObjectIsClosed(this))
+ GeneratorSetClosed(this);
+ throw e;
+ }
+}
+
+function StarGeneratorThrow(val) {
+ if (!IsSuspendedStarGenerator(this)) {
+ if (!IsObject(this) || !IsStarGeneratorObject(this))
+ return callFunction(CallStarGeneratorMethodIfWrapped, this, val, "StarGeneratorThrow");
+
+ if (StarGeneratorObjectIsClosed(this))
+ throw val;
+
+ if (GeneratorIsRunning(this))
+ ThrowTypeError(JSMSG_NESTING_GENERATOR);
+ }
+
+ try {
+ return resumeGenerator(this, val, 'throw');
+ } catch (e) {
+ if (!StarGeneratorObjectIsClosed(this))
+ GeneratorSetClosed(this);
+ throw e;
+ }
+}
+
+function StarGeneratorReturn(val) {
+ if (!IsSuspendedStarGenerator(this)) {
+ if (!IsObject(this) || !IsStarGeneratorObject(this))
+ return callFunction(CallStarGeneratorMethodIfWrapped, this, val, "StarGeneratorReturn");
+
+ if (StarGeneratorObjectIsClosed(this))
+ return { value: val, done: true };
+
+ if (GeneratorIsRunning(this))
+ ThrowTypeError(JSMSG_NESTING_GENERATOR);
+ }
+
+ try {
+ var rval = { value: val, done: true };
+ return resumeGenerator(this, rval, 'close');
+ } catch (e) {
+ if (!StarGeneratorObjectIsClosed(this))
+ GeneratorSetClosed(this);
+ throw e;
+ }
+}
+
+function LegacyGeneratorNext(val) {
+ if (!IsObject(this) || !IsLegacyGeneratorObject(this))
+ return callFunction(CallLegacyGeneratorMethodIfWrapped, this, val, "LegacyGeneratorNext");
+
+ if (LegacyGeneratorObjectIsClosed(this))
+ ThrowStopIteration();
+
+ if (GeneratorIsRunning(this))
+ ThrowTypeError(JSMSG_NESTING_GENERATOR);
+
+ try {
+ return resumeGenerator(this, val, 'next');
+ } catch(e) {
+ if (!LegacyGeneratorObjectIsClosed(this))
+ GeneratorSetClosed(this);
+ throw e;
+ }
+}
+_SetCanonicalName(LegacyGeneratorNext, "next");
+
+function LegacyGeneratorThrow(val) {
+ if (!IsObject(this) || !IsLegacyGeneratorObject(this))
+ return callFunction(CallLegacyGeneratorMethodIfWrapped, this, val, "LegacyGeneratorThrow");
+
+ if (LegacyGeneratorObjectIsClosed(this))
+ throw val;
+
+ if (GeneratorIsRunning(this))
+ ThrowTypeError(JSMSG_NESTING_GENERATOR);
+
+ try {
+ return resumeGenerator(this, val, 'throw');
+ } catch(e) {
+ if (!LegacyGeneratorObjectIsClosed(this))
+ GeneratorSetClosed(this);
+ throw e;
+ }
+}
+
+// Called by js::CloseIterator.
+function LegacyGeneratorCloseInternal() {
+ assert(IsObject(this), "Not an object: " + ToString(this));
+ assert(IsLegacyGeneratorObject(this), "Not a legacy generator object: " + ToString(this));
+ assert(!LegacyGeneratorObjectIsClosed(this), "Already closed: " + ToString(this));
+
+ if (GeneratorIsRunning(this))
+ ThrowTypeError(JSMSG_NESTING_GENERATOR);
+
+ resumeGenerator(this, undefined, 'close');
+ if (!LegacyGeneratorObjectIsClosed(this))
+ CloseClosingLegacyGeneratorObject(this);
+}
+
+function LegacyGeneratorClose() {
+ if (!IsObject(this) || !IsLegacyGeneratorObject(this))
+ return callFunction(CallLegacyGeneratorMethodIfWrapped, this, "LegacyGeneratorClose");
+
+ if (LegacyGeneratorObjectIsClosed(this))
+ return;
+
+ callFunction(LegacyGeneratorCloseInternal, this);
+}
+
+function InterpretGeneratorResume(gen, val, kind) {
+ // If we want to resume a generator in the interpreter, the script containing
+ // the resumeGenerator/JSOP_RESUME also has to run in the interpreter. The
+ // forceInterpreter() call below compiles to a bytecode op that prevents us
+ // from JITing this script.
+ forceInterpreter();
+ if (kind === "next")
+ return resumeGenerator(gen, val, "next");
+ if (kind === "throw")
+ return resumeGenerator(gen, val, "throw");
+ assert(kind === "close", "Invalid resume kind");
+ return resumeGenerator(gen, val, "close");
+}
diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp
new file mode 100644
index 000000000..990a4acdb
--- /dev/null
+++ b/js/src/builtin/Intl.cpp
@@ -0,0 +1,2990 @@
+/* -*- 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/. */
+
+/*
+ * The Intl module specified by standard ECMA-402,
+ * ECMAScript Internationalization API Specification.
+ */
+
+#include "builtin/Intl.h"
+
+#include "mozilla/PodOperations.h"
+#include "mozilla/Range.h"
+#include "mozilla/ScopeExit.h"
+
+#include <string.h>
+
+#include "jsapi.h"
+#include "jsatom.h"
+#include "jscntxt.h"
+#include "jsobj.h"
+
+#include "builtin/IntlTimeZoneData.h"
+#if ENABLE_INTL_API
+#include "unicode/ucal.h"
+#include "unicode/ucol.h"
+#include "unicode/udat.h"
+#include "unicode/udatpg.h"
+#include "unicode/uenum.h"
+#include "unicode/unum.h"
+#include "unicode/unumsys.h"
+#include "unicode/ustring.h"
+#endif
+#include "vm/DateTime.h"
+#include "vm/GlobalObject.h"
+#include "vm/Interpreter.h"
+#include "vm/Stack.h"
+#include "vm/StringBuffer.h"
+#include "vm/Unicode.h"
+
+#include "jsobjinlines.h"
+
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+using mozilla::IsFinite;
+using mozilla::IsNegativeZero;
+using mozilla::MakeScopeExit;
+using mozilla::PodCopy;
+
+
+/*
+ * Pervasive note: ICU functions taking a UErrorCode in/out parameter always
+ * test that parameter before doing anything, and will return immediately if
+ * the value indicates that a failure occurred in a prior ICU call,
+ * without doing anything else. See
+ * http://userguide.icu-project.org/design#TOC-Error-Handling
+ */
+
+
+/******************** ICU stubs ********************/
+
+#if !ENABLE_INTL_API
+
+/*
+ * When the Internationalization API isn't enabled, we also shouldn't link
+ * against ICU. However, we still want to compile this code in order to prevent
+ * bit rot. The following stub implementations for ICU functions make this
+ * possible. The functions using them should never be called, so they assert
+ * and return error codes. Signatures adapted from ICU header files locid.h,
+ * numsys.h, ucal.h, ucol.h, udat.h, udatpg.h, uenum.h, unum.h; see the ICU
+ * directory for license.
+ */
+
+typedef bool UBool;
+typedef char16_t UChar;
+typedef double UDate;
+
+enum UErrorCode {
+ U_ZERO_ERROR,
+ U_BUFFER_OVERFLOW_ERROR,
+};
+
+static inline UBool
+U_FAILURE(UErrorCode code)
+{
+ MOZ_CRASH("U_FAILURE: Intl API disabled");
+}
+
+inline const UChar*
+Char16ToUChar(const char16_t* chars)
+{
+ MOZ_CRASH("Char16ToUChar: Intl API disabled");
+}
+
+inline UChar*
+Char16ToUChar(char16_t* chars)
+{
+ MOZ_CRASH("Char16ToUChar: Intl API disabled");
+}
+
+static int32_t
+u_strlen(const UChar* s)
+{
+ MOZ_CRASH("u_strlen: Intl API disabled");
+}
+
+struct UEnumeration;
+
+static int32_t
+uenum_count(UEnumeration* en, UErrorCode* status)
+{
+ MOZ_CRASH("uenum_count: Intl API disabled");
+}
+
+static const char*
+uenum_next(UEnumeration* en, int32_t* resultLength, UErrorCode* status)
+{
+ MOZ_CRASH("uenum_next: Intl API disabled");
+}
+
+static void
+uenum_close(UEnumeration* en)
+{
+ MOZ_CRASH("uenum_close: Intl API disabled");
+}
+
+struct UCollator;
+
+enum UColAttribute {
+ UCOL_ALTERNATE_HANDLING,
+ UCOL_CASE_FIRST,
+ UCOL_CASE_LEVEL,
+ UCOL_NORMALIZATION_MODE,
+ UCOL_STRENGTH,
+ UCOL_NUMERIC_COLLATION,
+};
+
+enum UColAttributeValue {
+ UCOL_DEFAULT = -1,
+ UCOL_PRIMARY = 0,
+ UCOL_SECONDARY = 1,
+ UCOL_TERTIARY = 2,
+ UCOL_OFF = 16,
+ UCOL_ON = 17,
+ UCOL_SHIFTED = 20,
+ UCOL_LOWER_FIRST = 24,
+ UCOL_UPPER_FIRST = 25,
+};
+
+enum UCollationResult {
+ UCOL_EQUAL = 0,
+ UCOL_GREATER = 1,
+ UCOL_LESS = -1
+};
+
+static int32_t
+ucol_countAvailable()
+{
+ MOZ_CRASH("ucol_countAvailable: Intl API disabled");
+}
+
+static const char*
+ucol_getAvailable(int32_t localeIndex)
+{
+ MOZ_CRASH("ucol_getAvailable: Intl API disabled");
+}
+
+static UCollator*
+ucol_open(const char* loc, UErrorCode* status)
+{
+ MOZ_CRASH("ucol_open: Intl API disabled");
+}
+
+static void
+ucol_setAttribute(UCollator* coll, UColAttribute attr, UColAttributeValue value, UErrorCode* status)
+{
+ MOZ_CRASH("ucol_setAttribute: Intl API disabled");
+}
+
+static UCollationResult
+ucol_strcoll(const UCollator* coll, const UChar* source, int32_t sourceLength,
+ const UChar* target, int32_t targetLength)
+{
+ MOZ_CRASH("ucol_strcoll: Intl API disabled");
+}
+
+static void
+ucol_close(UCollator* coll)
+{
+ MOZ_CRASH("ucol_close: Intl API disabled");
+}
+
+static UEnumeration*
+ucol_getKeywordValuesForLocale(const char* key, const char* locale, UBool commonlyUsed,
+ UErrorCode* status)
+{
+ MOZ_CRASH("ucol_getKeywordValuesForLocale: Intl API disabled");
+}
+
+struct UParseError;
+struct UFieldPosition;
+struct UFieldPositionIterator;
+typedef void* UNumberFormat;
+
+enum UNumberFormatStyle {
+ UNUM_DECIMAL = 1,
+ UNUM_CURRENCY,
+ UNUM_PERCENT,
+ UNUM_CURRENCY_ISO,
+ UNUM_CURRENCY_PLURAL,
+};
+
+enum UNumberFormatRoundingMode {
+ UNUM_ROUND_HALFUP,
+};
+
+enum UNumberFormatAttribute {
+ UNUM_GROUPING_USED,
+ UNUM_MIN_INTEGER_DIGITS,
+ UNUM_MAX_FRACTION_DIGITS,
+ UNUM_MIN_FRACTION_DIGITS,
+ UNUM_ROUNDING_MODE,
+ UNUM_SIGNIFICANT_DIGITS_USED,
+ UNUM_MIN_SIGNIFICANT_DIGITS,
+ UNUM_MAX_SIGNIFICANT_DIGITS,
+};
+
+enum UNumberFormatTextAttribute {
+ UNUM_CURRENCY_CODE,
+};
+
+static int32_t
+unum_countAvailable()
+{
+ MOZ_CRASH("unum_countAvailable: Intl API disabled");
+}
+
+static const char*
+unum_getAvailable(int32_t localeIndex)
+{
+ MOZ_CRASH("unum_getAvailable: Intl API disabled");
+}
+
+static UNumberFormat*
+unum_open(UNumberFormatStyle style, const UChar* pattern, int32_t patternLength,
+ const char* locale, UParseError* parseErr, UErrorCode* status)
+{
+ MOZ_CRASH("unum_open: Intl API disabled");
+}
+
+static void
+unum_setAttribute(UNumberFormat* fmt, UNumberFormatAttribute attr, int32_t newValue)
+{
+ MOZ_CRASH("unum_setAttribute: Intl API disabled");
+}
+
+static int32_t
+unum_formatDouble(const UNumberFormat* fmt, double number, UChar* result,
+ int32_t resultLength, UFieldPosition* pos, UErrorCode* status)
+{
+ MOZ_CRASH("unum_formatDouble: Intl API disabled");
+}
+
+static void
+unum_close(UNumberFormat* fmt)
+{
+ MOZ_CRASH("unum_close: Intl API disabled");
+}
+
+static void
+unum_setTextAttribute(UNumberFormat* fmt, UNumberFormatTextAttribute tag, const UChar* newValue,
+ int32_t newValueLength, UErrorCode* status)
+{
+ MOZ_CRASH("unum_setTextAttribute: Intl API disabled");
+}
+
+typedef void* UNumberingSystem;
+
+static UNumberingSystem*
+unumsys_open(const char* locale, UErrorCode* status)
+{
+ MOZ_CRASH("unumsys_open: Intl API disabled");
+}
+
+static const char*
+unumsys_getName(const UNumberingSystem* unumsys)
+{
+ MOZ_CRASH("unumsys_getName: Intl API disabled");
+}
+
+static void
+unumsys_close(UNumberingSystem* unumsys)
+{
+ MOZ_CRASH("unumsys_close: Intl API disabled");
+}
+
+typedef void* UCalendar;
+
+enum UCalendarType {
+ UCAL_TRADITIONAL,
+ UCAL_DEFAULT = UCAL_TRADITIONAL,
+ UCAL_GREGORIAN
+};
+
+enum UCalendarAttribute {
+ UCAL_FIRST_DAY_OF_WEEK,
+ UCAL_MINIMAL_DAYS_IN_FIRST_WEEK
+};
+
+enum UCalendarDaysOfWeek {
+ UCAL_SUNDAY,
+ UCAL_MONDAY,
+ UCAL_TUESDAY,
+ UCAL_WEDNESDAY,
+ UCAL_THURSDAY,
+ UCAL_FRIDAY,
+ UCAL_SATURDAY
+};
+
+enum UCalendarWeekdayType {
+ UCAL_WEEKDAY,
+ UCAL_WEEKEND,
+ UCAL_WEEKEND_ONSET,
+ UCAL_WEEKEND_CEASE
+};
+
+enum UCalendarDateFields {
+ UCAL_ERA,
+ UCAL_YEAR,
+ UCAL_MONTH,
+ UCAL_WEEK_OF_YEAR,
+ UCAL_WEEK_OF_MONTH,
+ UCAL_DATE,
+ UCAL_DAY_OF_YEAR,
+ UCAL_DAY_OF_WEEK,
+ UCAL_DAY_OF_WEEK_IN_MONTH,
+ UCAL_AM_PM,
+ UCAL_HOUR,
+ UCAL_HOUR_OF_DAY,
+ UCAL_MINUTE,
+ UCAL_SECOND,
+ UCAL_MILLISECOND,
+ UCAL_ZONE_OFFSET,
+ UCAL_DST_OFFSET,
+ UCAL_YEAR_WOY,
+ UCAL_DOW_LOCAL,
+ UCAL_EXTENDED_YEAR,
+ UCAL_JULIAN_DAY,
+ UCAL_MILLISECONDS_IN_DAY,
+ UCAL_IS_LEAP_MONTH,
+ UCAL_FIELD_COUNT,
+ UCAL_DAY_OF_MONTH = UCAL_DATE
+};
+
+static UCalendar*
+ucal_open(const UChar* zoneID, int32_t len, const char* locale,
+ UCalendarType type, UErrorCode* status)
+{
+ MOZ_CRASH("ucal_open: Intl API disabled");
+}
+
+static const char*
+ucal_getType(const UCalendar* cal, UErrorCode* status)
+{
+ MOZ_CRASH("ucal_getType: Intl API disabled");
+}
+
+static UEnumeration*
+ucal_getKeywordValuesForLocale(const char* key, const char* locale,
+ UBool commonlyUsed, UErrorCode* status)
+{
+ MOZ_CRASH("ucal_getKeywordValuesForLocale: Intl API disabled");
+}
+
+static void
+ucal_close(UCalendar* cal)
+{
+ MOZ_CRASH("ucal_close: Intl API disabled");
+}
+
+static UCalendarWeekdayType
+ucal_getDayOfWeekType(const UCalendar *cal, UCalendarDaysOfWeek dayOfWeek, UErrorCode* status)
+{
+ MOZ_CRASH("ucal_getDayOfWeekType: Intl API disabled");
+}
+
+static int32_t
+ucal_getAttribute(const UCalendar* cal,
+ UCalendarAttribute attr)
+{
+ MOZ_CRASH("ucal_getAttribute: Intl API disabled");
+}
+
+static int32_t
+ucal_get(const UCalendar *cal, UCalendarDateFields field, UErrorCode *status)
+{
+ MOZ_CRASH("ucal_get: Intl API disabled");
+}
+
+static UEnumeration*
+ucal_openTimeZones(UErrorCode* status)
+{
+ MOZ_CRASH("ucal_openTimeZones: Intl API disabled");
+}
+
+static int32_t
+ucal_getCanonicalTimeZoneID(const UChar* id, int32_t len, UChar* result, int32_t resultCapacity,
+ UBool* isSystemID, UErrorCode* status)
+{
+ MOZ_CRASH("ucal_getCanonicalTimeZoneID: Intl API disabled");
+}
+
+static int32_t
+ucal_getDefaultTimeZone(UChar* result, int32_t resultCapacity, UErrorCode* status)
+{
+ MOZ_CRASH("ucal_getDefaultTimeZone: Intl API disabled");
+}
+
+typedef void* UDateTimePatternGenerator;
+
+static UDateTimePatternGenerator*
+udatpg_open(const char* locale, UErrorCode* pErrorCode)
+{
+ MOZ_CRASH("udatpg_open: Intl API disabled");
+}
+
+static int32_t
+udatpg_getBestPattern(UDateTimePatternGenerator* dtpg, const UChar* skeleton,
+ int32_t length, UChar* bestPattern, int32_t capacity,
+ UErrorCode* pErrorCode)
+{
+ MOZ_CRASH("udatpg_getBestPattern: Intl API disabled");
+}
+
+static void
+udatpg_close(UDateTimePatternGenerator* dtpg)
+{
+ MOZ_CRASH("udatpg_close: Intl API disabled");
+}
+
+typedef void* UCalendar;
+typedef void* UDateFormat;
+
+enum UDateFormatField {
+ UDAT_ERA_FIELD = 0,
+ UDAT_YEAR_FIELD = 1,
+ UDAT_MONTH_FIELD = 2,
+ UDAT_DATE_FIELD = 3,
+ UDAT_HOUR_OF_DAY1_FIELD = 4,
+ UDAT_HOUR_OF_DAY0_FIELD = 5,
+ UDAT_MINUTE_FIELD = 6,
+ UDAT_SECOND_FIELD = 7,
+ UDAT_FRACTIONAL_SECOND_FIELD = 8,
+ UDAT_DAY_OF_WEEK_FIELD = 9,
+ UDAT_DAY_OF_YEAR_FIELD = 10,
+ UDAT_DAY_OF_WEEK_IN_MONTH_FIELD = 11,
+ UDAT_WEEK_OF_YEAR_FIELD = 12,
+ UDAT_WEEK_OF_MONTH_FIELD = 13,
+ UDAT_AM_PM_FIELD = 14,
+ UDAT_HOUR1_FIELD = 15,
+ UDAT_HOUR0_FIELD = 16,
+ UDAT_TIMEZONE_FIELD = 17,
+ UDAT_YEAR_WOY_FIELD = 18,
+ UDAT_DOW_LOCAL_FIELD = 19,
+ UDAT_EXTENDED_YEAR_FIELD = 20,
+ UDAT_JULIAN_DAY_FIELD = 21,
+ UDAT_MILLISECONDS_IN_DAY_FIELD = 22,
+ UDAT_TIMEZONE_RFC_FIELD = 23,
+ UDAT_TIMEZONE_GENERIC_FIELD = 24,
+ UDAT_STANDALONE_DAY_FIELD = 25,
+ UDAT_STANDALONE_MONTH_FIELD = 26,
+ UDAT_QUARTER_FIELD = 27,
+ UDAT_STANDALONE_QUARTER_FIELD = 28,
+ UDAT_TIMEZONE_SPECIAL_FIELD = 29,
+ UDAT_YEAR_NAME_FIELD = 30,
+ UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD = 31,
+ UDAT_TIMEZONE_ISO_FIELD = 32,
+ UDAT_TIMEZONE_ISO_LOCAL_FIELD = 33,
+ UDAT_RELATED_YEAR_FIELD = 34,
+ UDAT_AM_PM_MIDNIGHT_NOON_FIELD = 35,
+ UDAT_FLEXIBLE_DAY_PERIOD_FIELD = 36,
+ UDAT_TIME_SEPARATOR_FIELD = 37,
+ UDAT_FIELD_COUNT = 38
+};
+
+enum UDateFormatStyle {
+ UDAT_PATTERN = -2,
+ UDAT_IGNORE = UDAT_PATTERN
+};
+
+static int32_t
+udat_countAvailable()
+{
+ MOZ_CRASH("udat_countAvailable: Intl API disabled");
+}
+
+static const char*
+udat_getAvailable(int32_t localeIndex)
+{
+ MOZ_CRASH("udat_getAvailable: Intl API disabled");
+}
+
+static UDateFormat*
+udat_open(UDateFormatStyle timeStyle, UDateFormatStyle dateStyle, const char* locale,
+ const UChar* tzID, int32_t tzIDLength, const UChar* pattern,
+ int32_t patternLength, UErrorCode* status)
+{
+ MOZ_CRASH("udat_open: Intl API disabled");
+}
+
+static const UCalendar*
+udat_getCalendar(const UDateFormat* fmt)
+{
+ MOZ_CRASH("udat_getCalendar: Intl API disabled");
+}
+
+static void
+ucal_setGregorianChange(UCalendar* cal, UDate date, UErrorCode* pErrorCode)
+{
+ MOZ_CRASH("ucal_setGregorianChange: Intl API disabled");
+}
+
+static int32_t
+udat_format(const UDateFormat* format, UDate dateToFormat, UChar* result,
+ int32_t resultLength, UFieldPosition* position, UErrorCode* status)
+{
+ MOZ_CRASH("udat_format: Intl API disabled");
+}
+
+static int32_t
+udat_formatForFields(const UDateFormat* format, UDate dateToFormat,
+ UChar* result, int32_t resultLength, UFieldPositionIterator* fpositer,
+ UErrorCode* status)
+{
+ MOZ_CRASH("udat_formatForFields: Intl API disabled");
+}
+
+static UFieldPositionIterator*
+ufieldpositer_open(UErrorCode* status)
+{
+ MOZ_CRASH("ufieldpositer_open: Intl API disabled");
+}
+
+static void
+ufieldpositer_close(UFieldPositionIterator* fpositer)
+{
+ MOZ_CRASH("ufieldpositer_close: Intl API disabled");
+}
+
+static int32_t
+ufieldpositer_next(UFieldPositionIterator* fpositer, int32_t* beginIndex, int32_t* endIndex)
+{
+ MOZ_CRASH("ufieldpositer_next: Intl API disabled");
+}
+
+static void
+udat_close(UDateFormat* format)
+{
+ MOZ_CRASH("udat_close: Intl API disabled");
+}
+
+#endif
+
+
+/******************** Common to Intl constructors ********************/
+
+static bool
+IntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
+ HandleValue locales, HandleValue options)
+{
+ RootedValue initializerValue(cx);
+ if (!GlobalObject::getIntrinsicValue(cx, cx->global(), initializer, &initializerValue))
+ return false;
+ MOZ_ASSERT(initializerValue.isObject());
+ MOZ_ASSERT(initializerValue.toObject().is<JSFunction>());
+
+ FixedInvokeArgs<3> args(cx);
+
+ args[0].setObject(*obj);
+ args[1].set(locales);
+ args[2].set(options);
+
+ RootedValue thisv(cx, NullValue());
+ RootedValue ignored(cx);
+ return js::Call(cx, initializerValue, thisv, args, &ignored);
+}
+
+static bool
+CreateDefaultOptions(JSContext* cx, MutableHandleValue defaultOptions)
+{
+ RootedObject options(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
+ if (!options)
+ return false;
+ defaultOptions.setObject(*options);
+ return true;
+}
+
+// CountAvailable and GetAvailable describe the signatures used for ICU API
+// to determine available locales for various functionality.
+typedef int32_t
+(* CountAvailable)();
+
+typedef const char*
+(* GetAvailable)(int32_t localeIndex);
+
+static bool
+intl_availableLocales(JSContext* cx, CountAvailable countAvailable,
+ GetAvailable getAvailable, MutableHandleValue result)
+{
+ RootedObject locales(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
+ if (!locales)
+ return false;
+
+#if ENABLE_INTL_API
+ uint32_t count = countAvailable();
+ RootedValue t(cx, BooleanValue(true));
+ for (uint32_t i = 0; i < count; i++) {
+ const char* locale = getAvailable(i);
+ auto lang = DuplicateString(cx, locale);
+ if (!lang)
+ return false;
+ char* p;
+ while ((p = strchr(lang.get(), '_')))
+ *p = '-';
+ RootedAtom a(cx, Atomize(cx, lang.get(), strlen(lang.get())));
+ if (!a)
+ return false;
+ if (!DefineProperty(cx, locales, a->asPropertyName(), t, nullptr, nullptr,
+ JSPROP_ENUMERATE))
+ {
+ return false;
+ }
+ }
+#endif
+ result.setObject(*locales);
+ return true;
+}
+
+/**
+ * Returns the object holding the internal properties for obj.
+ */
+static JSObject*
+GetInternals(JSContext* cx, HandleObject obj)
+{
+ RootedValue getInternalsValue(cx);
+ if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().getInternals,
+ &getInternalsValue))
+ {
+ return nullptr;
+ }
+ MOZ_ASSERT(getInternalsValue.isObject());
+ MOZ_ASSERT(getInternalsValue.toObject().is<JSFunction>());
+
+ FixedInvokeArgs<1> args(cx);
+
+ args[0].setObject(*obj);
+
+ RootedValue v(cx, NullValue());
+ if (!js::Call(cx, getInternalsValue, v, args, &v))
+ return nullptr;
+
+ return &v.toObject();
+}
+
+static bool
+equal(const char* s1, const char* s2)
+{
+ return !strcmp(s1, s2);
+}
+
+static bool
+equal(JSAutoByteString& s1, const char* s2)
+{
+ return !strcmp(s1.ptr(), s2);
+}
+
+static const char*
+icuLocale(const char* locale)
+{
+ if (equal(locale, "und"))
+ return ""; // ICU root locale
+ return locale;
+}
+
+// Simple RAII for ICU objects. Unfortunately, ICU's C++ API is uniformly
+// unstable, so we can't use its smart pointers for this.
+template <typename T, void (Delete)(T*)>
+class ScopedICUObject
+{
+ T* ptr_;
+
+ public:
+ explicit ScopedICUObject(T* ptr)
+ : ptr_(ptr)
+ {}
+
+ ~ScopedICUObject() {
+ if (ptr_)
+ Delete(ptr_);
+ }
+
+ // In cases where an object should be deleted on abnormal exits,
+ // but returned to the caller if everything goes well, call forget()
+ // to transfer the object just before returning.
+ T* forget() {
+ T* tmp = ptr_;
+ ptr_ = nullptr;
+ return tmp;
+ }
+};
+
+// The inline capacity we use for the char16_t Vectors.
+static const size_t INITIAL_CHAR_BUFFER_SIZE = 32;
+
+/******************** Collator ********************/
+
+static void collator_finalize(FreeOp* fop, JSObject* obj);
+
+static const uint32_t UCOLLATOR_SLOT = 0;
+static const uint32_t COLLATOR_SLOTS_COUNT = 1;
+
+static const ClassOps CollatorClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ collator_finalize
+};
+
+static const Class CollatorClass = {
+ js_Object_str,
+ JSCLASS_HAS_RESERVED_SLOTS(COLLATOR_SLOTS_COUNT) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &CollatorClassOps
+};
+
+#if JS_HAS_TOSOURCE
+static bool
+collator_toSource(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setString(cx->names().Collator);
+ return true;
+}
+#endif
+
+static const JSFunctionSpec collator_static_methods[] = {
+ JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_Collator_supportedLocalesOf", 1, 0),
+ JS_FS_END
+};
+
+static const JSFunctionSpec collator_methods[] = {
+ JS_SELF_HOSTED_FN("resolvedOptions", "Intl_Collator_resolvedOptions", 0, 0),
+#if JS_HAS_TOSOURCE
+ JS_FN(js_toSource_str, collator_toSource, 0, 0),
+#endif
+ JS_FS_END
+};
+
+/**
+ * Collator constructor.
+ * Spec: ECMAScript Internationalization API Specification, 10.1
+ */
+static bool
+Collator(JSContext* cx, const CallArgs& args, bool construct)
+{
+ RootedObject obj(cx);
+
+ if (!construct) {
+ // 10.1.2.1 step 3
+ JSObject* intl = cx->global()->getOrCreateIntlObject(cx);
+ if (!intl)
+ return false;
+ RootedValue self(cx, args.thisv());
+ if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
+ // 10.1.2.1 step 4
+ obj = ToObject(cx, self);
+ if (!obj)
+ return false;
+
+ // 10.1.2.1 step 5
+ bool extensible;
+ if (!IsExtensible(cx, obj, &extensible))
+ return false;
+ if (!extensible)
+ return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
+ } else {
+ // 10.1.2.1 step 3.a
+ construct = true;
+ }
+ }
+ if (construct) {
+ // 10.1.3.1 paragraph 2
+ RootedObject proto(cx, cx->global()->getOrCreateCollatorPrototype(cx));
+ if (!proto)
+ return false;
+ obj = NewObjectWithGivenProto(cx, &CollatorClass, proto);
+ if (!obj)
+ return false;
+
+ obj->as<NativeObject>().setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr));
+ }
+
+ // 10.1.2.1 steps 1 and 2; 10.1.3.1 steps 1 and 2
+ RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue());
+ RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue());
+
+ // 10.1.2.1 step 6; 10.1.3.1 step 3
+ if (!IntlInitialize(cx, obj, cx->names().InitializeCollator, locales, options))
+ return false;
+
+ // 10.1.2.1 steps 3.a and 7
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool
+Collator(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return Collator(cx, args, args.isConstructing());
+}
+
+bool
+js::intl_Collator(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 2);
+ // intl_Collator is an intrinsic for self-hosted JavaScript, so it cannot
+ // be used with "new", but it still has to be treated as a constructor.
+ return Collator(cx, args, true);
+}
+
+static void
+collator_finalize(FreeOp* fop, JSObject* obj)
+{
+ MOZ_ASSERT(fop->onMainThread());
+
+ // This is-undefined check shouldn't be necessary, but for internal
+ // brokenness in object allocation code. For the moment, hack around it by
+ // explicitly guarding against the possibility of the reserved slot not
+ // containing a private. See bug 949220.
+ const Value& slot = obj->as<NativeObject>().getReservedSlot(UCOLLATOR_SLOT);
+ if (!slot.isUndefined()) {
+ if (UCollator* coll = static_cast<UCollator*>(slot.toPrivate()))
+ ucol_close(coll);
+ }
+}
+
+static JSObject*
+CreateCollatorPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
+{
+ RootedFunction ctor(cx, global->createConstructor(cx, &Collator, cx->names().Collator, 0));
+ if (!ctor)
+ return nullptr;
+
+ RootedNativeObject proto(cx, global->createBlankPrototype(cx, &CollatorClass));
+ if (!proto)
+ return nullptr;
+ proto->setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr));
+
+ if (!LinkConstructorAndPrototype(cx, ctor, proto))
+ return nullptr;
+
+ // 10.2.2
+ if (!JS_DefineFunctions(cx, ctor, collator_static_methods))
+ return nullptr;
+
+ // 10.3.2 and 10.3.3
+ if (!JS_DefineFunctions(cx, proto, collator_methods))
+ return nullptr;
+
+ /*
+ * Install the getter for Collator.prototype.compare, which returns a bound
+ * comparison function for the specified Collator object (suitable for
+ * passing to methods like Array.prototype.sort).
+ */
+ RootedValue getter(cx);
+ if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().CollatorCompareGet, &getter))
+ return nullptr;
+ if (!DefineProperty(cx, proto, cx->names().compare, UndefinedHandleValue,
+ JS_DATA_TO_FUNC_PTR(JSGetterOp, &getter.toObject()),
+ nullptr, JSPROP_GETTER | JSPROP_SHARED))
+ {
+ return nullptr;
+ }
+
+ RootedValue options(cx);
+ if (!CreateDefaultOptions(cx, &options))
+ return nullptr;
+
+ // 10.2.1 and 10.3
+ if (!IntlInitialize(cx, proto, cx->names().InitializeCollator, UndefinedHandleValue, options))
+ return nullptr;
+
+ // 8.1
+ RootedValue ctorValue(cx, ObjectValue(*ctor));
+ if (!DefineProperty(cx, Intl, cx->names().Collator, ctorValue, nullptr, nullptr, 0))
+ return nullptr;
+
+ return proto;
+}
+
+bool
+js::intl_Collator_availableLocales(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 0);
+
+ RootedValue result(cx);
+ if (!intl_availableLocales(cx, ucol_countAvailable, ucol_getAvailable, &result))
+ return false;
+ args.rval().set(result);
+ return true;
+}
+
+bool
+js::intl_availableCollations(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(args[0].isString());
+
+ JSAutoByteString locale(cx, args[0].toString());
+ if (!locale)
+ return false;
+ UErrorCode status = U_ZERO_ERROR;
+ UEnumeration* values = ucol_getKeywordValuesForLocale("co", locale.ptr(), false, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+ ScopedICUObject<UEnumeration, uenum_close> toClose(values);
+
+ uint32_t count = uenum_count(values, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ RootedObject collations(cx, NewDenseEmptyArray(cx));
+ if (!collations)
+ return false;
+
+ uint32_t index = 0;
+ for (uint32_t i = 0; i < count; i++) {
+ const char* collation = uenum_next(values, nullptr, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ // Per ECMA-402, 10.2.3, we don't include standard and search:
+ // "The values 'standard' and 'search' must not be used as elements in
+ // any [[sortLocaleData]][locale].co and [[searchLocaleData]][locale].co
+ // array."
+ if (equal(collation, "standard") || equal(collation, "search"))
+ continue;
+
+ // ICU returns old-style keyword values; map them to BCP 47 equivalents
+ // (see http://bugs.icu-project.org/trac/ticket/9620).
+ if (equal(collation, "dictionary"))
+ collation = "dict";
+ else if (equal(collation, "gb2312han"))
+ collation = "gb2312";
+ else if (equal(collation, "phonebook"))
+ collation = "phonebk";
+ else if (equal(collation, "traditional"))
+ collation = "trad";
+
+ RootedString jscollation(cx, JS_NewStringCopyZ(cx, collation));
+ if (!jscollation)
+ return false;
+ RootedValue element(cx, StringValue(jscollation));
+ if (!DefineElement(cx, collations, index++, element))
+ return false;
+ }
+
+ args.rval().setObject(*collations);
+ return true;
+}
+
+/**
+ * Returns a new UCollator with the locale and collation options
+ * of the given Collator.
+ */
+static UCollator*
+NewUCollator(JSContext* cx, HandleObject collator)
+{
+ RootedValue value(cx);
+
+ RootedObject internals(cx, GetInternals(cx, collator));
+ if (!internals)
+ return nullptr;
+
+ if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
+ return nullptr;
+ JSAutoByteString locale(cx, value.toString());
+ if (!locale)
+ return nullptr;
+
+ // UCollator options with default values.
+ UColAttributeValue uStrength = UCOL_DEFAULT;
+ UColAttributeValue uCaseLevel = UCOL_OFF;
+ UColAttributeValue uAlternate = UCOL_DEFAULT;
+ UColAttributeValue uNumeric = UCOL_OFF;
+ // Normalization is always on to meet the canonical equivalence requirement.
+ UColAttributeValue uNormalization = UCOL_ON;
+ UColAttributeValue uCaseFirst = UCOL_DEFAULT;
+
+ if (!GetProperty(cx, internals, internals, cx->names().usage, &value))
+ return nullptr;
+ JSAutoByteString usage(cx, value.toString());
+ if (!usage)
+ return nullptr;
+ if (equal(usage, "search")) {
+ // ICU expects search as a Unicode locale extension on locale.
+ // Unicode locale extensions must occur before private use extensions.
+ const char* oldLocale = locale.ptr();
+ const char* p;
+ size_t index;
+ size_t localeLen = strlen(oldLocale);
+ if ((p = strstr(oldLocale, "-x-")))
+ index = p - oldLocale;
+ else
+ index = localeLen;
+
+ const char* insert;
+ if ((p = strstr(oldLocale, "-u-")) && static_cast<size_t>(p - oldLocale) < index) {
+ index = p - oldLocale + 2;
+ insert = "-co-search";
+ } else {
+ insert = "-u-co-search";
+ }
+ size_t insertLen = strlen(insert);
+ char* newLocale = cx->pod_malloc<char>(localeLen + insertLen + 1);
+ if (!newLocale)
+ return nullptr;
+ memcpy(newLocale, oldLocale, index);
+ memcpy(newLocale + index, insert, insertLen);
+ memcpy(newLocale + index + insertLen, oldLocale + index, localeLen - index + 1); // '\0'
+ locale.clear();
+ locale.initBytes(newLocale);
+ }
+
+ // We don't need to look at the collation property - it can only be set
+ // via the Unicode locale extension and is therefore already set on
+ // locale.
+
+ if (!GetProperty(cx, internals, internals, cx->names().sensitivity, &value))
+ return nullptr;
+ JSAutoByteString sensitivity(cx, value.toString());
+ if (!sensitivity)
+ return nullptr;
+ if (equal(sensitivity, "base")) {
+ uStrength = UCOL_PRIMARY;
+ } else if (equal(sensitivity, "accent")) {
+ uStrength = UCOL_SECONDARY;
+ } else if (equal(sensitivity, "case")) {
+ uStrength = UCOL_PRIMARY;
+ uCaseLevel = UCOL_ON;
+ } else {
+ MOZ_ASSERT(equal(sensitivity, "variant"));
+ uStrength = UCOL_TERTIARY;
+ }
+
+ if (!GetProperty(cx, internals, internals, cx->names().ignorePunctuation, &value))
+ return nullptr;
+ // According to the ICU team, UCOL_SHIFTED causes punctuation to be
+ // ignored. Looking at Unicode Technical Report 35, Unicode Locale Data
+ // Markup Language, "shifted" causes whitespace and punctuation to be
+ // ignored - that's a bit more than asked for, but there's no way to get
+ // less.
+ if (value.toBoolean())
+ uAlternate = UCOL_SHIFTED;
+
+ if (!GetProperty(cx, internals, internals, cx->names().numeric, &value))
+ return nullptr;
+ if (!value.isUndefined() && value.toBoolean())
+ uNumeric = UCOL_ON;
+
+ if (!GetProperty(cx, internals, internals, cx->names().caseFirst, &value))
+ return nullptr;
+ if (!value.isUndefined()) {
+ JSAutoByteString caseFirst(cx, value.toString());
+ if (!caseFirst)
+ return nullptr;
+ if (equal(caseFirst, "upper"))
+ uCaseFirst = UCOL_UPPER_FIRST;
+ else if (equal(caseFirst, "lower"))
+ uCaseFirst = UCOL_LOWER_FIRST;
+ else
+ MOZ_ASSERT(equal(caseFirst, "false"));
+ }
+
+ UErrorCode status = U_ZERO_ERROR;
+ UCollator* coll = ucol_open(icuLocale(locale.ptr()), &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return nullptr;
+ }
+
+ ucol_setAttribute(coll, UCOL_STRENGTH, uStrength, &status);
+ ucol_setAttribute(coll, UCOL_CASE_LEVEL, uCaseLevel, &status);
+ ucol_setAttribute(coll, UCOL_ALTERNATE_HANDLING, uAlternate, &status);
+ ucol_setAttribute(coll, UCOL_NUMERIC_COLLATION, uNumeric, &status);
+ ucol_setAttribute(coll, UCOL_NORMALIZATION_MODE, uNormalization, &status);
+ ucol_setAttribute(coll, UCOL_CASE_FIRST, uCaseFirst, &status);
+ if (U_FAILURE(status)) {
+ ucol_close(coll);
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return nullptr;
+ }
+
+ return coll;
+}
+
+static bool
+intl_CompareStrings(JSContext* cx, UCollator* coll, HandleString str1, HandleString str2,
+ MutableHandleValue result)
+{
+ MOZ_ASSERT(str1);
+ MOZ_ASSERT(str2);
+
+ if (str1 == str2) {
+ result.setInt32(0);
+ return true;
+ }
+
+ AutoStableStringChars stableChars1(cx);
+ if (!stableChars1.initTwoByte(cx, str1))
+ return false;
+
+ AutoStableStringChars stableChars2(cx);
+ if (!stableChars2.initTwoByte(cx, str2))
+ return false;
+
+ mozilla::Range<const char16_t> chars1 = stableChars1.twoByteRange();
+ mozilla::Range<const char16_t> chars2 = stableChars2.twoByteRange();
+
+ UCollationResult uresult = ucol_strcoll(coll,
+ Char16ToUChar(chars1.begin().get()), chars1.length(),
+ Char16ToUChar(chars2.begin().get()), chars2.length());
+ int32_t res;
+ switch (uresult) {
+ case UCOL_LESS: res = -1; break;
+ case UCOL_EQUAL: res = 0; break;
+ case UCOL_GREATER: res = 1; break;
+ default: MOZ_CRASH("ucol_strcoll returned bad UCollationResult");
+ }
+ result.setInt32(res);
+ return true;
+}
+
+bool
+js::intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 3);
+ MOZ_ASSERT(args[0].isObject());
+ MOZ_ASSERT(args[1].isString());
+ MOZ_ASSERT(args[2].isString());
+
+ RootedObject collator(cx, &args[0].toObject());
+
+ // Obtain a UCollator object, cached if possible.
+ // XXX Does this handle Collator instances from other globals correctly?
+ bool isCollatorInstance = collator->getClass() == &CollatorClass;
+ UCollator* coll;
+ if (isCollatorInstance) {
+ void* priv = collator->as<NativeObject>().getReservedSlot(UCOLLATOR_SLOT).toPrivate();
+ coll = static_cast<UCollator*>(priv);
+ if (!coll) {
+ coll = NewUCollator(cx, collator);
+ if (!coll)
+ return false;
+ collator->as<NativeObject>().setReservedSlot(UCOLLATOR_SLOT, PrivateValue(coll));
+ }
+ } else {
+ // There's no good place to cache the ICU collator for an object
+ // that has been initialized as a Collator but is not a Collator
+ // instance. One possibility might be to add a Collator instance as an
+ // internal property to each such object.
+ coll = NewUCollator(cx, collator);
+ if (!coll)
+ return false;
+ }
+
+ // Use the UCollator to actually compare the strings.
+ RootedString str1(cx, args[1].toString());
+ RootedString str2(cx, args[2].toString());
+ RootedValue result(cx);
+ bool success = intl_CompareStrings(cx, coll, str1, str2, &result);
+
+ if (!isCollatorInstance)
+ ucol_close(coll);
+ if (!success)
+ return false;
+ args.rval().set(result);
+ return true;
+}
+
+
+/******************** NumberFormat ********************/
+
+static void numberFormat_finalize(FreeOp* fop, JSObject* obj);
+
+static const uint32_t UNUMBER_FORMAT_SLOT = 0;
+static const uint32_t NUMBER_FORMAT_SLOTS_COUNT = 1;
+
+static const ClassOps NumberFormatClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ numberFormat_finalize
+};
+
+static const Class NumberFormatClass = {
+ js_Object_str,
+ JSCLASS_HAS_RESERVED_SLOTS(NUMBER_FORMAT_SLOTS_COUNT) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &NumberFormatClassOps
+};
+
+#if JS_HAS_TOSOURCE
+static bool
+numberFormat_toSource(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setString(cx->names().NumberFormat);
+ return true;
+}
+#endif
+
+static const JSFunctionSpec numberFormat_static_methods[] = {
+ JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_NumberFormat_supportedLocalesOf", 1, 0),
+ JS_FS_END
+};
+
+static const JSFunctionSpec numberFormat_methods[] = {
+ JS_SELF_HOSTED_FN("resolvedOptions", "Intl_NumberFormat_resolvedOptions", 0, 0),
+#if JS_HAS_TOSOURCE
+ JS_FN(js_toSource_str, numberFormat_toSource, 0, 0),
+#endif
+ JS_FS_END
+};
+
+/**
+ * NumberFormat constructor.
+ * Spec: ECMAScript Internationalization API Specification, 11.1
+ */
+static bool
+NumberFormat(JSContext* cx, const CallArgs& args, bool construct)
+{
+ RootedObject obj(cx);
+
+ if (!construct) {
+ // 11.1.2.1 step 3
+ JSObject* intl = cx->global()->getOrCreateIntlObject(cx);
+ if (!intl)
+ return false;
+ RootedValue self(cx, args.thisv());
+ if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
+ // 11.1.2.1 step 4
+ obj = ToObject(cx, self);
+ if (!obj)
+ return false;
+
+ // 11.1.2.1 step 5
+ bool extensible;
+ if (!IsExtensible(cx, obj, &extensible))
+ return false;
+ if (!extensible)
+ return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
+ } else {
+ // 11.1.2.1 step 3.a
+ construct = true;
+ }
+ }
+ if (construct) {
+ // 11.1.3.1 paragraph 2
+ RootedObject proto(cx, cx->global()->getOrCreateNumberFormatPrototype(cx));
+ if (!proto)
+ return false;
+ obj = NewObjectWithGivenProto(cx, &NumberFormatClass, proto);
+ if (!obj)
+ return false;
+
+ obj->as<NativeObject>().setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
+ }
+
+ // 11.1.2.1 steps 1 and 2; 11.1.3.1 steps 1 and 2
+ RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue());
+ RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue());
+
+ // 11.1.2.1 step 6; 11.1.3.1 step 3
+ if (!IntlInitialize(cx, obj, cx->names().InitializeNumberFormat, locales, options))
+ return false;
+
+ // 11.1.2.1 steps 3.a and 7
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool
+NumberFormat(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return NumberFormat(cx, args, args.isConstructing());
+}
+
+bool
+js::intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 2);
+ // intl_NumberFormat is an intrinsic for self-hosted JavaScript, so it
+ // cannot be used with "new", but it still has to be treated as a
+ // constructor.
+ return NumberFormat(cx, args, true);
+}
+
+static void
+numberFormat_finalize(FreeOp* fop, JSObject* obj)
+{
+ MOZ_ASSERT(fop->onMainThread());
+
+ // This is-undefined check shouldn't be necessary, but for internal
+ // brokenness in object allocation code. For the moment, hack around it by
+ // explicitly guarding against the possibility of the reserved slot not
+ // containing a private. See bug 949220.
+ const Value& slot = obj->as<NativeObject>().getReservedSlot(UNUMBER_FORMAT_SLOT);
+ if (!slot.isUndefined()) {
+ if (UNumberFormat* nf = static_cast<UNumberFormat*>(slot.toPrivate()))
+ unum_close(nf);
+ }
+}
+
+static JSObject*
+CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
+{
+ RootedFunction ctor(cx);
+ ctor = global->createConstructor(cx, &NumberFormat, cx->names().NumberFormat, 0);
+ if (!ctor)
+ return nullptr;
+
+ RootedNativeObject proto(cx, global->createBlankPrototype(cx, &NumberFormatClass));
+ if (!proto)
+ return nullptr;
+ proto->setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
+
+ if (!LinkConstructorAndPrototype(cx, ctor, proto))
+ return nullptr;
+
+ // 11.2.2
+ if (!JS_DefineFunctions(cx, ctor, numberFormat_static_methods))
+ return nullptr;
+
+ // 11.3.2 and 11.3.3
+ if (!JS_DefineFunctions(cx, proto, numberFormat_methods))
+ return nullptr;
+
+ /*
+ * Install the getter for NumberFormat.prototype.format, which returns a
+ * bound formatting function for the specified NumberFormat object (suitable
+ * for passing to methods like Array.prototype.map).
+ */
+ RootedValue getter(cx);
+ if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().NumberFormatFormatGet,
+ &getter))
+ {
+ return nullptr;
+ }
+ if (!DefineProperty(cx, proto, cx->names().format, UndefinedHandleValue,
+ JS_DATA_TO_FUNC_PTR(JSGetterOp, &getter.toObject()),
+ nullptr, JSPROP_GETTER | JSPROP_SHARED))
+ {
+ return nullptr;
+ }
+
+ RootedValue options(cx);
+ if (!CreateDefaultOptions(cx, &options))
+ return nullptr;
+
+ // 11.2.1 and 11.3
+ if (!IntlInitialize(cx, proto, cx->names().InitializeNumberFormat, UndefinedHandleValue,
+ options))
+ {
+ return nullptr;
+ }
+
+ // 8.1
+ RootedValue ctorValue(cx, ObjectValue(*ctor));
+ if (!DefineProperty(cx, Intl, cx->names().NumberFormat, ctorValue, nullptr, nullptr, 0))
+ return nullptr;
+
+ return proto;
+}
+
+bool
+js::intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 0);
+
+ RootedValue result(cx);
+ if (!intl_availableLocales(cx, unum_countAvailable, unum_getAvailable, &result))
+ return false;
+ args.rval().set(result);
+ return true;
+}
+
+bool
+js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(args[0].isString());
+
+ JSAutoByteString locale(cx, args[0].toString());
+ if (!locale)
+ return false;
+
+ UErrorCode status = U_ZERO_ERROR;
+ UNumberingSystem* numbers = unumsys_open(icuLocale(locale.ptr()), &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ ScopedICUObject<UNumberingSystem, unumsys_close> toClose(numbers);
+
+ const char* name = unumsys_getName(numbers);
+ RootedString jsname(cx, JS_NewStringCopyZ(cx, name));
+ if (!jsname)
+ return false;
+
+ args.rval().setString(jsname);
+ return true;
+}
+
+/**
+ * Returns a new UNumberFormat with the locale and number formatting options
+ * of the given NumberFormat.
+ */
+static UNumberFormat*
+NewUNumberFormat(JSContext* cx, HandleObject numberFormat)
+{
+ RootedValue value(cx);
+
+ RootedObject internals(cx, GetInternals(cx, numberFormat));
+ if (!internals)
+ return nullptr;
+
+ if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
+ return nullptr;
+ JSAutoByteString locale(cx, value.toString());
+ if (!locale)
+ return nullptr;
+
+ // UNumberFormat options with default values
+ UNumberFormatStyle uStyle = UNUM_DECIMAL;
+ const UChar* uCurrency = nullptr;
+ uint32_t uMinimumIntegerDigits = 1;
+ uint32_t uMinimumFractionDigits = 0;
+ uint32_t uMaximumFractionDigits = 3;
+ int32_t uMinimumSignificantDigits = -1;
+ int32_t uMaximumSignificantDigits = -1;
+ bool uUseGrouping = true;
+
+ // Sprinkle appropriate rooting flavor over things the GC might care about.
+ RootedString currency(cx);
+ AutoStableStringChars stableChars(cx);
+
+ // We don't need to look at numberingSystem - it can only be set via
+ // the Unicode locale extension and is therefore already set on locale.
+
+ if (!GetProperty(cx, internals, internals, cx->names().style, &value))
+ return nullptr;
+ JSAutoByteString style(cx, value.toString());
+ if (!style)
+ return nullptr;
+
+ if (equal(style, "currency")) {
+ if (!GetProperty(cx, internals, internals, cx->names().currency, &value))
+ return nullptr;
+ currency = value.toString();
+ MOZ_ASSERT(currency->length() == 3,
+ "IsWellFormedCurrencyCode permits only length-3 strings");
+ if (!currency->ensureFlat(cx) || !stableChars.initTwoByte(cx, currency))
+ return nullptr;
+ // uCurrency remains owned by stableChars.
+ uCurrency = Char16ToUChar(stableChars.twoByteRange().begin().get());
+ if (!uCurrency)
+ return nullptr;
+
+ if (!GetProperty(cx, internals, internals, cx->names().currencyDisplay, &value))
+ return nullptr;
+ JSAutoByteString currencyDisplay(cx, value.toString());
+ if (!currencyDisplay)
+ return nullptr;
+ if (equal(currencyDisplay, "code")) {
+ uStyle = UNUM_CURRENCY_ISO;
+ } else if (equal(currencyDisplay, "symbol")) {
+ uStyle = UNUM_CURRENCY;
+ } else {
+ MOZ_ASSERT(equal(currencyDisplay, "name"));
+ uStyle = UNUM_CURRENCY_PLURAL;
+ }
+ } else if (equal(style, "percent")) {
+ uStyle = UNUM_PERCENT;
+ } else {
+ MOZ_ASSERT(equal(style, "decimal"));
+ uStyle = UNUM_DECIMAL;
+ }
+
+ RootedId id(cx, NameToId(cx->names().minimumSignificantDigits));
+ bool hasP;
+ if (!HasProperty(cx, internals, id, &hasP))
+ return nullptr;
+ if (hasP) {
+ if (!GetProperty(cx, internals, internals, cx->names().minimumSignificantDigits,
+ &value))
+ {
+ return nullptr;
+ }
+ uMinimumSignificantDigits = int32_t(value.toNumber());
+ if (!GetProperty(cx, internals, internals, cx->names().maximumSignificantDigits,
+ &value))
+ {
+ return nullptr;
+ }
+ uMaximumSignificantDigits = int32_t(value.toNumber());
+ } else {
+ if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
+ &value))
+ {
+ return nullptr;
+ }
+ uMinimumIntegerDigits = int32_t(value.toNumber());
+ if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits,
+ &value))
+ {
+ return nullptr;
+ }
+ uMinimumFractionDigits = int32_t(value.toNumber());
+ if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits,
+ &value))
+ {
+ return nullptr;
+ }
+ uMaximumFractionDigits = int32_t(value.toNumber());
+ }
+
+ if (!GetProperty(cx, internals, internals, cx->names().useGrouping, &value))
+ return nullptr;
+ uUseGrouping = value.toBoolean();
+
+ UErrorCode status = U_ZERO_ERROR;
+ UNumberFormat* nf = unum_open(uStyle, nullptr, 0, icuLocale(locale.ptr()), nullptr, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return nullptr;
+ }
+ ScopedICUObject<UNumberFormat, unum_close> toClose(nf);
+
+ if (uCurrency) {
+ unum_setTextAttribute(nf, UNUM_CURRENCY_CODE, uCurrency, 3, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return nullptr;
+ }
+ }
+ if (uMinimumSignificantDigits != -1) {
+ unum_setAttribute(nf, UNUM_SIGNIFICANT_DIGITS_USED, true);
+ unum_setAttribute(nf, UNUM_MIN_SIGNIFICANT_DIGITS, uMinimumSignificantDigits);
+ unum_setAttribute(nf, UNUM_MAX_SIGNIFICANT_DIGITS, uMaximumSignificantDigits);
+ } else {
+ unum_setAttribute(nf, UNUM_MIN_INTEGER_DIGITS, uMinimumIntegerDigits);
+ unum_setAttribute(nf, UNUM_MIN_FRACTION_DIGITS, uMinimumFractionDigits);
+ unum_setAttribute(nf, UNUM_MAX_FRACTION_DIGITS, uMaximumFractionDigits);
+ }
+ unum_setAttribute(nf, UNUM_GROUPING_USED, uUseGrouping);
+ unum_setAttribute(nf, UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP);
+
+ return toClose.forget();
+}
+
+static bool
+intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result)
+{
+ // FormatNumber doesn't consider -0.0 to be negative.
+ if (IsNegativeZero(x))
+ x = 0.0;
+
+ Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
+ if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
+ return false;
+ UErrorCode status = U_ZERO_ERROR;
+ int size = unum_formatDouble(nf, x, Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE,
+ nullptr, &status);
+ if (status == U_BUFFER_OVERFLOW_ERROR) {
+ if (!chars.resize(size))
+ return false;
+ status = U_ZERO_ERROR;
+ unum_formatDouble(nf, x, Char16ToUChar(chars.begin()), size, nullptr, &status);
+ }
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size);
+ if (!str)
+ return false;
+
+ result.setString(str);
+ return true;
+}
+
+bool
+js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 2);
+ MOZ_ASSERT(args[0].isObject());
+ MOZ_ASSERT(args[1].isNumber());
+
+ RootedObject numberFormat(cx, &args[0].toObject());
+
+ // Obtain a UNumberFormat object, cached if possible.
+ bool isNumberFormatInstance = numberFormat->getClass() == &NumberFormatClass;
+ UNumberFormat* nf;
+ if (isNumberFormatInstance) {
+ void* priv =
+ numberFormat->as<NativeObject>().getReservedSlot(UNUMBER_FORMAT_SLOT).toPrivate();
+ nf = static_cast<UNumberFormat*>(priv);
+ if (!nf) {
+ nf = NewUNumberFormat(cx, numberFormat);
+ if (!nf)
+ return false;
+ numberFormat->as<NativeObject>().setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nf));
+ }
+ } else {
+ // There's no good place to cache the ICU number format for an object
+ // that has been initialized as a NumberFormat but is not a
+ // NumberFormat instance. One possibility might be to add a
+ // NumberFormat instance as an internal property to each such object.
+ nf = NewUNumberFormat(cx, numberFormat);
+ if (!nf)
+ return false;
+ }
+
+ // Use the UNumberFormat to actually format the number.
+ RootedValue result(cx);
+ bool success = intl_FormatNumber(cx, nf, args[1].toNumber(), &result);
+
+ if (!isNumberFormatInstance)
+ unum_close(nf);
+ if (!success)
+ return false;
+ args.rval().set(result);
+ return true;
+}
+
+
+/******************** DateTimeFormat ********************/
+
+static void dateTimeFormat_finalize(FreeOp* fop, JSObject* obj);
+
+static const uint32_t UDATE_FORMAT_SLOT = 0;
+static const uint32_t DATE_TIME_FORMAT_SLOTS_COUNT = 1;
+
+static const ClassOps DateTimeFormatClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ dateTimeFormat_finalize
+};
+
+static const Class DateTimeFormatClass = {
+ js_Object_str,
+ JSCLASS_HAS_RESERVED_SLOTS(DATE_TIME_FORMAT_SLOTS_COUNT) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &DateTimeFormatClassOps
+};
+
+#if JS_HAS_TOSOURCE
+static bool
+dateTimeFormat_toSource(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setString(cx->names().DateTimeFormat);
+ return true;
+}
+#endif
+
+static const JSFunctionSpec dateTimeFormat_static_methods[] = {
+ JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_DateTimeFormat_supportedLocalesOf", 1, 0),
+ JS_FS_END
+};
+
+static const JSFunctionSpec dateTimeFormat_methods[] = {
+ JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DateTimeFormat_resolvedOptions", 0, 0),
+ JS_SELF_HOSTED_FN("formatToParts", "Intl_DateTimeFormat_formatToParts", 0, 0),
+#if JS_HAS_TOSOURCE
+ JS_FN(js_toSource_str, dateTimeFormat_toSource, 0, 0),
+#endif
+ JS_FS_END
+};
+
+/**
+ * DateTimeFormat constructor.
+ * Spec: ECMAScript Internationalization API Specification, 12.1
+ */
+static bool
+DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct)
+{
+ RootedObject obj(cx);
+
+ if (!construct) {
+ // 12.1.2.1 step 3
+ JSObject* intl = cx->global()->getOrCreateIntlObject(cx);
+ if (!intl)
+ return false;
+ RootedValue self(cx, args.thisv());
+ if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
+ // 12.1.2.1 step 4
+ obj = ToObject(cx, self);
+ if (!obj)
+ return false;
+
+ // 12.1.2.1 step 5
+ bool extensible;
+ if (!IsExtensible(cx, obj, &extensible))
+ return false;
+ if (!extensible)
+ return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
+ } else {
+ // 12.1.2.1 step 3.a
+ construct = true;
+ }
+ }
+ if (construct) {
+ // 12.1.3.1 paragraph 2
+ RootedObject proto(cx, cx->global()->getOrCreateDateTimeFormatPrototype(cx));
+ if (!proto)
+ return false;
+ obj = NewObjectWithGivenProto(cx, &DateTimeFormatClass, proto);
+ if (!obj)
+ return false;
+
+ obj->as<NativeObject>().setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr));
+ }
+
+ // 12.1.2.1 steps 1 and 2; 12.1.3.1 steps 1 and 2
+ RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue());
+ RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue());
+
+ // 12.1.2.1 step 6; 12.1.3.1 step 3
+ if (!IntlInitialize(cx, obj, cx->names().InitializeDateTimeFormat, locales, options))
+ return false;
+
+ // 12.1.2.1 steps 3.a and 7
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool
+DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return DateTimeFormat(cx, args, args.isConstructing());
+}
+
+bool
+js::intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 2);
+ // intl_DateTimeFormat is an intrinsic for self-hosted JavaScript, so it
+ // cannot be used with "new", but it still has to be treated as a
+ // constructor.
+ return DateTimeFormat(cx, args, true);
+}
+
+static void
+dateTimeFormat_finalize(FreeOp* fop, JSObject* obj)
+{
+ MOZ_ASSERT(fop->onMainThread());
+
+ // This is-undefined check shouldn't be necessary, but for internal
+ // brokenness in object allocation code. For the moment, hack around it by
+ // explicitly guarding against the possibility of the reserved slot not
+ // containing a private. See bug 949220.
+ const Value& slot = obj->as<NativeObject>().getReservedSlot(UDATE_FORMAT_SLOT);
+ if (!slot.isUndefined()) {
+ if (UDateFormat* df = static_cast<UDateFormat*>(slot.toPrivate()))
+ udat_close(df);
+ }
+}
+
+static JSObject*
+CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
+{
+ RootedFunction ctor(cx);
+ ctor = global->createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0);
+ if (!ctor)
+ return nullptr;
+
+ RootedNativeObject proto(cx, global->createBlankPrototype(cx, &DateTimeFormatClass));
+ if (!proto)
+ return nullptr;
+ proto->setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr));
+
+ if (!LinkConstructorAndPrototype(cx, ctor, proto))
+ return nullptr;
+
+ // 12.2.2
+ if (!JS_DefineFunctions(cx, ctor, dateTimeFormat_static_methods))
+ return nullptr;
+
+ // 12.3.2 and 12.3.3
+ if (!JS_DefineFunctions(cx, proto, dateTimeFormat_methods))
+ return nullptr;
+
+ // Install a getter for DateTimeFormat.prototype.format that returns a
+ // formatting function bound to a specified DateTimeFormat object (suitable
+ // for passing to methods like Array.prototype.map).
+ RootedValue getter(cx);
+ if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().DateTimeFormatFormatGet,
+ &getter))
+ {
+ return nullptr;
+ }
+ if (!DefineProperty(cx, proto, cx->names().format, UndefinedHandleValue,
+ JS_DATA_TO_FUNC_PTR(JSGetterOp, &getter.toObject()),
+ nullptr, JSPROP_GETTER | JSPROP_SHARED))
+ {
+ return nullptr;
+ }
+
+ RootedValue options(cx);
+ if (!CreateDefaultOptions(cx, &options))
+ return nullptr;
+
+ // 12.2.1 and 12.3
+ if (!IntlInitialize(cx, proto, cx->names().InitializeDateTimeFormat, UndefinedHandleValue,
+ options))
+ {
+ return nullptr;
+ }
+
+ // 8.1
+ RootedValue ctorValue(cx, ObjectValue(*ctor));
+ if (!DefineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, nullptr, nullptr, 0))
+ return nullptr;
+
+ return proto;
+}
+
+bool
+js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 0);
+
+ RootedValue result(cx);
+ if (!intl_availableLocales(cx, udat_countAvailable, udat_getAvailable, &result))
+ return false;
+ args.rval().set(result);
+ return true;
+}
+
+// ICU returns old-style keyword values; map them to BCP 47 equivalents
+// (see http://bugs.icu-project.org/trac/ticket/9620).
+static const char*
+bcp47CalendarName(const char* icuName)
+{
+ if (equal(icuName, "ethiopic-amete-alem"))
+ return "ethioaa";
+ if (equal(icuName, "gregorian"))
+ return "gregory";
+ if (equal(icuName, "islamic-civil"))
+ return "islamicc";
+ return icuName;
+}
+
+bool
+js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(args[0].isString());
+
+ JSAutoByteString locale(cx, args[0].toString());
+ if (!locale)
+ return false;
+
+ RootedObject calendars(cx, NewDenseEmptyArray(cx));
+ if (!calendars)
+ return false;
+ uint32_t index = 0;
+
+ // We need the default calendar for the locale as the first result.
+ UErrorCode status = U_ZERO_ERROR;
+ RootedString jscalendar(cx);
+ {
+ UCalendar* cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status);
+
+ // This correctly handles nullptr |cal| when opening failed.
+ ScopedICUObject<UCalendar, ucal_close> closeCalendar(cal);
+
+ const char* calendar = ucal_getType(cal, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar));
+ if (!jscalendar)
+ return false;
+ }
+
+ RootedValue element(cx, StringValue(jscalendar));
+ if (!DefineElement(cx, calendars, index++, element))
+ return false;
+
+ // Now get the calendars that "would make a difference", i.e., not the default.
+ UEnumeration* values = ucal_getKeywordValuesForLocale("ca", locale.ptr(), false, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+ ScopedICUObject<UEnumeration, uenum_close> toClose(values);
+
+ uint32_t count = uenum_count(values, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ for (; count > 0; count--) {
+ const char* calendar = uenum_next(values, nullptr, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ jscalendar = JS_NewStringCopyZ(cx, bcp47CalendarName(calendar));
+ if (!jscalendar)
+ return false;
+ element = StringValue(jscalendar);
+ if (!DefineElement(cx, calendars, index++, element))
+ return false;
+ }
+
+ args.rval().setObject(*calendars);
+ return true;
+}
+
+template<typename Char>
+static constexpr Char
+ToUpperASCII(Char c)
+{
+ return ('a' <= c && c <= 'z')
+ ? (c & ~0x20)
+ : c;
+}
+
+static_assert(ToUpperASCII('a') == 'A', "verifying 'a' uppercases correctly");
+static_assert(ToUpperASCII('m') == 'M', "verifying 'm' uppercases correctly");
+static_assert(ToUpperASCII('z') == 'Z', "verifying 'z' uppercases correctly");
+static_assert(ToUpperASCII(u'a') == u'A', "verifying u'a' uppercases correctly");
+static_assert(ToUpperASCII(u'k') == u'K', "verifying u'k' uppercases correctly");
+static_assert(ToUpperASCII(u'z') == u'Z', "verifying u'z' uppercases correctly");
+
+template<typename Char1, typename Char2>
+static bool
+EqualCharsIgnoreCaseASCII(const Char1* s1, const Char2* s2, size_t len)
+{
+ for (const Char1* s1end = s1 + len; s1 < s1end; s1++, s2++) {
+ if (ToUpperASCII(*s1) != ToUpperASCII(*s2))
+ return false;
+ }
+ return true;
+}
+
+template<typename Char>
+static js::HashNumber
+HashStringIgnoreCaseASCII(const Char* s, size_t length)
+{
+ uint32_t hash = 0;
+ for (size_t i = 0; i < length; i++)
+ hash = mozilla::AddToHash(hash, ToUpperASCII(s[i]));
+ return hash;
+}
+
+js::SharedIntlData::TimeZoneHasher::Lookup::Lookup(JSFlatString* timeZone)
+ : isLatin1(timeZone->hasLatin1Chars()), length(timeZone->length())
+{
+ if (isLatin1) {
+ latin1Chars = timeZone->latin1Chars(nogc);
+ hash = HashStringIgnoreCaseASCII(latin1Chars, length);
+ } else {
+ twoByteChars = timeZone->twoByteChars(nogc);
+ hash = HashStringIgnoreCaseASCII(twoByteChars, length);
+ }
+}
+
+bool
+js::SharedIntlData::TimeZoneHasher::match(TimeZoneName key, const Lookup& lookup)
+{
+ if (key->length() != lookup.length)
+ return false;
+
+ // Compare time zone names ignoring ASCII case differences.
+ if (key->hasLatin1Chars()) {
+ const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
+ if (lookup.isLatin1)
+ return EqualCharsIgnoreCaseASCII(keyChars, lookup.latin1Chars, lookup.length);
+ return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars, lookup.length);
+ }
+
+ const char16_t* keyChars = key->twoByteChars(lookup.nogc);
+ if (lookup.isLatin1)
+ return EqualCharsIgnoreCaseASCII(lookup.latin1Chars, keyChars, lookup.length);
+ return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars, lookup.length);
+}
+
+static bool
+IsLegacyICUTimeZone(const char* timeZone)
+{
+ for (const auto& legacyTimeZone : js::timezone::legacyICUTimeZones) {
+ if (equal(timeZone, legacyTimeZone))
+ return true;
+ }
+ return false;
+}
+
+bool
+js::SharedIntlData::ensureTimeZones(JSContext* cx)
+{
+ if (timeZoneDataInitialized)
+ return true;
+
+ // If initTimeZones() was called previously, but didn't complete due to
+ // OOM, clear all sets/maps and start from scratch.
+ if (availableTimeZones.initialized())
+ availableTimeZones.finish();
+ if (!availableTimeZones.init()) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ UErrorCode status = U_ZERO_ERROR;
+ UEnumeration* values = ucal_openTimeZones(&status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+ ScopedICUObject<UEnumeration, uenum_close> toClose(values);
+
+ RootedAtom timeZone(cx);
+ while (true) {
+ int32_t size;
+ const char* rawTimeZone = uenum_next(values, &size, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ if (rawTimeZone == nullptr)
+ break;
+
+ // Skip legacy ICU time zone names.
+ if (IsLegacyICUTimeZone(rawTimeZone))
+ continue;
+
+ MOZ_ASSERT(size >= 0);
+ timeZone = Atomize(cx, rawTimeZone, size_t(size));
+ if (!timeZone)
+ return false;
+
+ TimeZoneHasher::Lookup lookup(timeZone);
+ TimeZoneSet::AddPtr p = availableTimeZones.lookupForAdd(lookup);
+
+ // ICU shouldn't report any duplicate time zone names, but if it does,
+ // just ignore the duplicate name.
+ if (!p && !availableTimeZones.add(p, timeZone)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ if (ianaZonesTreatedAsLinksByICU.initialized())
+ ianaZonesTreatedAsLinksByICU.finish();
+ if (!ianaZonesTreatedAsLinksByICU.init()) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ for (const char* rawTimeZone : timezone::ianaZonesTreatedAsLinksByICU) {
+ MOZ_ASSERT(rawTimeZone != nullptr);
+ timeZone = Atomize(cx, rawTimeZone, strlen(rawTimeZone));
+ if (!timeZone)
+ return false;
+
+ TimeZoneHasher::Lookup lookup(timeZone);
+ TimeZoneSet::AddPtr p = ianaZonesTreatedAsLinksByICU.lookupForAdd(lookup);
+ MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaZonesTreatedAsLinksByICU");
+
+ if (!ianaZonesTreatedAsLinksByICU.add(p, timeZone)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ if (ianaLinksCanonicalizedDifferentlyByICU.initialized())
+ ianaLinksCanonicalizedDifferentlyByICU.finish();
+ if (!ianaLinksCanonicalizedDifferentlyByICU.init()) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ RootedAtom linkName(cx);
+ RootedAtom& target = timeZone;
+ for (const auto& linkAndTarget : timezone::ianaLinksCanonicalizedDifferentlyByICU) {
+ const char* rawLinkName = linkAndTarget.link;
+ const char* rawTarget = linkAndTarget.target;
+
+ MOZ_ASSERT(rawLinkName != nullptr);
+ linkName = Atomize(cx, rawLinkName, strlen(rawLinkName));
+ if (!linkName)
+ return false;
+
+ MOZ_ASSERT(rawTarget != nullptr);
+ target = Atomize(cx, rawTarget, strlen(rawTarget));
+ if (!target)
+ return false;
+
+ TimeZoneHasher::Lookup lookup(linkName);
+ TimeZoneMap::AddPtr p = ianaLinksCanonicalizedDifferentlyByICU.lookupForAdd(lookup);
+ MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaLinksCanonicalizedDifferentlyByICU");
+
+ if (!ianaLinksCanonicalizedDifferentlyByICU.add(p, linkName, target)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(!timeZoneDataInitialized, "ensureTimeZones is neither reentrant nor thread-safe");
+ timeZoneDataInitialized = true;
+
+ return true;
+}
+
+bool
+js::SharedIntlData::validateTimeZoneName(JSContext* cx, HandleString timeZone,
+ MutableHandleString result)
+{
+ if (!ensureTimeZones(cx))
+ return false;
+
+ Rooted<JSFlatString*> timeZoneFlat(cx, timeZone->ensureFlat(cx));
+ if (!timeZoneFlat)
+ return false;
+
+ TimeZoneHasher::Lookup lookup(timeZoneFlat);
+ if (TimeZoneSet::Ptr p = availableTimeZones.lookup(lookup))
+ result.set(*p);
+
+ return true;
+}
+
+bool
+js::SharedIntlData::tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, HandleString timeZone,
+ MutableHandleString result)
+{
+ if (!ensureTimeZones(cx))
+ return false;
+
+ Rooted<JSFlatString*> timeZoneFlat(cx, timeZone->ensureFlat(cx));
+ if (!timeZoneFlat)
+ return false;
+
+ TimeZoneHasher::Lookup lookup(timeZoneFlat);
+ MOZ_ASSERT(availableTimeZones.has(lookup), "Invalid time zone name");
+
+ if (TimeZoneMap::Ptr p = ianaLinksCanonicalizedDifferentlyByICU.lookup(lookup)) {
+ // The effectively supported time zones aren't known at compile time,
+ // when
+ // 1. SpiderMonkey was compiled with "--with-system-icu".
+ // 2. ICU's dynamic time zone data loading feature was used.
+ // (ICU supports loading time zone files at runtime through the
+ // ICU_TIMEZONE_FILES_DIR environment variable.)
+ // Ensure ICU supports the new target zone before applying the update.
+ TimeZoneName targetTimeZone = p->value();
+ TimeZoneHasher::Lookup targetLookup(targetTimeZone);
+ if (availableTimeZones.has(targetLookup))
+ result.set(targetTimeZone);
+ } else if (TimeZoneSet::Ptr p = ianaZonesTreatedAsLinksByICU.lookup(lookup)) {
+ result.set(*p);
+ }
+
+ return true;
+}
+
+void
+js::SharedIntlData::destroyInstance()
+{
+ availableTimeZones.finish();
+ ianaZonesTreatedAsLinksByICU.finish();
+ ianaLinksCanonicalizedDifferentlyByICU.finish();
+}
+
+void
+js::SharedIntlData::trace(JSTracer* trc)
+{
+ // Atoms are always tenured.
+ if (!trc->runtime()->isHeapMinorCollecting()) {
+ availableTimeZones.trace(trc);
+ ianaZonesTreatedAsLinksByICU.trace(trc);
+ ianaLinksCanonicalizedDifferentlyByICU.trace(trc);
+ }
+}
+
+size_t
+js::SharedIntlData::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
+{
+ return availableTimeZones.sizeOfExcludingThis(mallocSizeOf) +
+ ianaZonesTreatedAsLinksByICU.sizeOfExcludingThis(mallocSizeOf) +
+ ianaLinksCanonicalizedDifferentlyByICU.sizeOfExcludingThis(mallocSizeOf);
+}
+
+bool
+js::intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(args[0].isString());
+
+ SharedIntlData& sharedIntlData = cx->sharedIntlData;
+
+ RootedString timeZone(cx, args[0].toString());
+ RootedString validatedTimeZone(cx);
+ if (!sharedIntlData.validateTimeZoneName(cx, timeZone, &validatedTimeZone))
+ return false;
+
+ if (validatedTimeZone)
+ args.rval().setString(validatedTimeZone);
+ else
+ args.rval().setNull();
+
+ return true;
+}
+
+bool
+js::intl_canonicalizeTimeZone(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(args[0].isString());
+
+ SharedIntlData& sharedIntlData = cx->sharedIntlData;
+
+ // Some time zone names are canonicalized differently by ICU -- handle
+ // those first:
+ RootedString timeZone(cx, args[0].toString());
+ RootedString ianaTimeZone(cx);
+ if (!sharedIntlData.tryCanonicalizeTimeZoneConsistentWithIANA(cx, timeZone, &ianaTimeZone))
+ return false;
+
+ if (ianaTimeZone) {
+ args.rval().setString(ianaTimeZone);
+ return true;
+ }
+
+ AutoStableStringChars stableChars(cx);
+ if (!stableChars.initTwoByte(cx, timeZone))
+ return false;
+
+ mozilla::Range<const char16_t> tzchars = stableChars.twoByteRange();
+
+ Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
+ if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
+ return false;
+
+ UBool* isSystemID = nullptr;
+ UErrorCode status = U_ZERO_ERROR;
+ int32_t size = ucal_getCanonicalTimeZoneID(Char16ToUChar(tzchars.begin().get()),
+ tzchars.length(), Char16ToUChar(chars.begin()),
+ INITIAL_CHAR_BUFFER_SIZE, isSystemID, &status);
+ if (status == U_BUFFER_OVERFLOW_ERROR) {
+ MOZ_ASSERT(size >= 0);
+ if (!chars.resize(size_t(size)))
+ return false;
+ status = U_ZERO_ERROR;
+ ucal_getCanonicalTimeZoneID(Char16ToUChar(tzchars.begin().get()), tzchars.length(),
+ Char16ToUChar(chars.begin()), size, isSystemID, &status);
+ }
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ MOZ_ASSERT(size >= 0);
+ JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size_t(size));
+ if (!str)
+ return false;
+ args.rval().setString(str);
+ return true;
+}
+
+bool
+js::intl_defaultTimeZone(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 0);
+
+ // The current default might be stale, because JS::ResetTimeZone() doesn't
+ // immediately update ICU's default time zone. So perform an update if
+ // needed.
+ js::ResyncICUDefaultTimeZone();
+
+ Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
+ if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
+ return false;
+
+ UErrorCode status = U_ZERO_ERROR;
+ int32_t size = ucal_getDefaultTimeZone(Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE,
+ &status);
+ if (status == U_BUFFER_OVERFLOW_ERROR) {
+ MOZ_ASSERT(size >= 0);
+ if (!chars.resize(size_t(size)))
+ return false;
+ status = U_ZERO_ERROR;
+ ucal_getDefaultTimeZone(Char16ToUChar(chars.begin()), size, &status);
+ }
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ MOZ_ASSERT(size >= 0);
+ JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size_t(size));
+ if (!str)
+ return false;
+ args.rval().setString(str);
+ return true;
+}
+
+bool
+js::intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 0);
+
+ UErrorCode status = U_ZERO_ERROR;
+ const UChar* uTimeZone = nullptr;
+ int32_t uTimeZoneLength = 0;
+ const char* rootLocale = "";
+ UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, rootLocale, UCAL_DEFAULT, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+ ScopedICUObject<UCalendar, ucal_close> toClose(cal);
+
+ int32_t offset = ucal_get(cal, UCAL_ZONE_OFFSET, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ args.rval().setInt32(offset);
+ return true;
+}
+
+bool
+js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 2);
+ MOZ_ASSERT(args[0].isString());
+ MOZ_ASSERT(args[1].isString());
+
+ JSAutoByteString locale(cx, args[0].toString());
+ if (!locale)
+ return false;
+
+ JSFlatString* skeletonFlat = args[1].toString()->ensureFlat(cx);
+ if (!skeletonFlat)
+ return false;
+
+ AutoStableStringChars stableChars(cx);
+ if (!stableChars.initTwoByte(cx, skeletonFlat))
+ return false;
+
+ mozilla::Range<const char16_t> skeletonChars = stableChars.twoByteRange();
+ uint32_t skeletonLen = u_strlen(Char16ToUChar(skeletonChars.begin().get()));
+
+ UErrorCode status = U_ZERO_ERROR;
+ UDateTimePatternGenerator* gen = udatpg_open(icuLocale(locale.ptr()), &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+ ScopedICUObject<UDateTimePatternGenerator, udatpg_close> toClose(gen);
+
+ int32_t size = udatpg_getBestPattern(gen, Char16ToUChar(skeletonChars.begin().get()),
+ skeletonLen, nullptr, 0, &status);
+ if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+ ScopedJSFreePtr<UChar> pattern(cx->pod_malloc<UChar>(size + 1));
+ if (!pattern)
+ return false;
+ pattern[size] = '\0';
+ status = U_ZERO_ERROR;
+ udatpg_getBestPattern(gen, Char16ToUChar(skeletonChars.begin().get()),
+ skeletonLen, pattern, size, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ RootedString str(cx, JS_NewUCStringCopyZ(cx, reinterpret_cast<char16_t*>(pattern.get())));
+ if (!str)
+ return false;
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Returns a new UDateFormat with the locale and date-time formatting options
+ * of the given DateTimeFormat.
+ */
+static UDateFormat*
+NewUDateFormat(JSContext* cx, HandleObject dateTimeFormat)
+{
+ RootedValue value(cx);
+
+ RootedObject internals(cx, GetInternals(cx, dateTimeFormat));
+ if (!internals)
+ return nullptr;
+
+ if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
+ return nullptr;
+ JSAutoByteString locale(cx, value.toString());
+ if (!locale)
+ return nullptr;
+
+ // We don't need to look at calendar and numberingSystem - they can only be
+ // set via the Unicode locale extension and are therefore already set on
+ // locale.
+
+ if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value))
+ return nullptr;
+
+ AutoStableStringChars timeZoneChars(cx);
+ Rooted<JSFlatString*> timeZoneFlat(cx, value.toString()->ensureFlat(cx));
+ if (!timeZoneFlat || !timeZoneChars.initTwoByte(cx, timeZoneFlat))
+ return nullptr;
+
+ const UChar* uTimeZone = Char16ToUChar(timeZoneChars.twoByteRange().begin().get());
+ uint32_t uTimeZoneLength = u_strlen(uTimeZone);
+
+ if (!GetProperty(cx, internals, internals, cx->names().pattern, &value))
+ return nullptr;
+
+ AutoStableStringChars patternChars(cx);
+ Rooted<JSFlatString*> patternFlat(cx, value.toString()->ensureFlat(cx));
+ if (!patternFlat || !patternChars.initTwoByte(cx, patternFlat))
+ return nullptr;
+
+ const UChar* uPattern = Char16ToUChar(patternChars.twoByteRange().begin().get());
+ uint32_t uPatternLength = u_strlen(uPattern);
+
+ UErrorCode status = U_ZERO_ERROR;
+ UDateFormat* df =
+ udat_open(UDAT_PATTERN, UDAT_PATTERN, icuLocale(locale.ptr()), uTimeZone, uTimeZoneLength,
+ uPattern, uPatternLength, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return nullptr;
+ }
+
+ // ECMAScript requires the Gregorian calendar to be used from the beginning
+ // of ECMAScript time.
+ UCalendar* cal = const_cast<UCalendar*>(udat_getCalendar(df));
+ ucal_setGregorianChange(cal, StartOfTime, &status);
+
+ // An error here means the calendar is not Gregorian, so we don't care.
+
+ return df;
+}
+
+static bool
+intl_FormatDateTime(JSContext* cx, UDateFormat* df, double x, MutableHandleValue result)
+{
+ if (!IsFinite(x)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE);
+ return false;
+ }
+
+ Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
+ if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
+ return false;
+ UErrorCode status = U_ZERO_ERROR;
+ int size = udat_format(df, x, Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE,
+ nullptr, &status);
+ if (status == U_BUFFER_OVERFLOW_ERROR) {
+ if (!chars.resize(size))
+ return false;
+ status = U_ZERO_ERROR;
+ udat_format(df, x, Char16ToUChar(chars.begin()), size, nullptr, &status);
+ }
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size);
+ if (!str)
+ return false;
+
+ result.setString(str);
+
+ return true;
+}
+
+using FieldType = ImmutablePropertyNamePtr JSAtomState::*;
+
+static FieldType
+GetFieldTypeForFormatField(UDateFormatField fieldName)
+{
+ // See intl/icu/source/i18n/unicode/udat.h for a detailed field list. This
+ // switch is deliberately exhaustive: cases might have to be added/removed
+ // if this code is compiled with a different ICU with more
+ // UDateFormatField enum initializers. Please guard such cases with
+ // appropriate ICU version-testing #ifdefs, should cross-version divergence
+ // occur.
+ switch (fieldName) {
+ case UDAT_ERA_FIELD:
+ return &JSAtomState::era;
+ case UDAT_YEAR_FIELD:
+ case UDAT_YEAR_WOY_FIELD:
+ case UDAT_EXTENDED_YEAR_FIELD:
+ case UDAT_YEAR_NAME_FIELD:
+ return &JSAtomState::year;
+
+ case UDAT_MONTH_FIELD:
+ case UDAT_STANDALONE_MONTH_FIELD:
+ return &JSAtomState::month;
+
+ case UDAT_DATE_FIELD:
+ case UDAT_JULIAN_DAY_FIELD:
+ return &JSAtomState::day;
+
+ case UDAT_HOUR_OF_DAY1_FIELD:
+ case UDAT_HOUR_OF_DAY0_FIELD:
+ case UDAT_HOUR1_FIELD:
+ case UDAT_HOUR0_FIELD:
+ return &JSAtomState::hour;
+
+ case UDAT_MINUTE_FIELD:
+ return &JSAtomState::minute;
+
+ case UDAT_SECOND_FIELD:
+ return &JSAtomState::second;
+
+ case UDAT_DAY_OF_WEEK_FIELD:
+ case UDAT_STANDALONE_DAY_FIELD:
+ case UDAT_DOW_LOCAL_FIELD:
+ case UDAT_DAY_OF_WEEK_IN_MONTH_FIELD:
+ return &JSAtomState::weekday;
+
+ case UDAT_AM_PM_FIELD:
+ return &JSAtomState::dayPeriod;
+
+ case UDAT_TIMEZONE_FIELD:
+ return &JSAtomState::timeZoneName;
+
+ case UDAT_FRACTIONAL_SECOND_FIELD:
+ case UDAT_DAY_OF_YEAR_FIELD:
+ case UDAT_WEEK_OF_YEAR_FIELD:
+ case UDAT_WEEK_OF_MONTH_FIELD:
+ case UDAT_MILLISECONDS_IN_DAY_FIELD:
+ case UDAT_TIMEZONE_RFC_FIELD:
+ case UDAT_TIMEZONE_GENERIC_FIELD:
+ case UDAT_QUARTER_FIELD:
+ case UDAT_STANDALONE_QUARTER_FIELD:
+ case UDAT_TIMEZONE_SPECIAL_FIELD:
+ case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
+ case UDAT_TIMEZONE_ISO_FIELD:
+ case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
+#ifndef U_HIDE_INTERNAL_API
+ case UDAT_RELATED_YEAR_FIELD:
+#endif
+#ifndef U_HIDE_DRAFT_API
+ case UDAT_AM_PM_MIDNIGHT_NOON_FIELD:
+ case UDAT_FLEXIBLE_DAY_PERIOD_FIELD:
+#endif
+#ifndef U_HIDE_INTERNAL_API
+ case UDAT_TIME_SEPARATOR_FIELD:
+#endif
+ // These fields are all unsupported.
+ return nullptr;
+
+ case UDAT_FIELD_COUNT:
+ MOZ_ASSERT_UNREACHABLE("format field sentinel value returned by "
+ "iterator!");
+ }
+
+ MOZ_ASSERT_UNREACHABLE("unenumerated, undocumented format field returned "
+ "by iterator");
+ return nullptr;
+}
+
+static bool
+intl_FormatToPartsDateTime(JSContext* cx, UDateFormat* df, double x, MutableHandleValue result)
+{
+ if (!IsFinite(x)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE);
+ return false;
+ }
+
+ Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
+ if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
+ return false;
+
+ UErrorCode status = U_ZERO_ERROR;
+ UFieldPositionIterator* fpositer = ufieldpositer_open(&status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+ auto closeFieldPosIter = MakeScopeExit([&]() { ufieldpositer_close(fpositer); });
+
+ int resultSize =
+ udat_formatForFields(df, x, Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE,
+ fpositer, &status);
+ if (status == U_BUFFER_OVERFLOW_ERROR) {
+ if (!chars.resize(resultSize))
+ return false;
+ status = U_ZERO_ERROR;
+ udat_formatForFields(df, x, Char16ToUChar(chars.begin()), resultSize, fpositer, &status);
+ }
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
+ if (!partsArray)
+ return false;
+ if (resultSize == 0) {
+ // An empty string contains no parts, so avoid extra work below.
+ result.setObject(*partsArray);
+ return true;
+ }
+
+ RootedString overallResult(cx, NewStringCopyN<CanGC>(cx, chars.begin(), resultSize));
+ if (!overallResult)
+ return false;
+
+ size_t lastEndIndex = 0;
+
+ uint32_t partIndex = 0;
+ RootedObject singlePart(cx);
+ RootedValue partType(cx);
+ RootedString partSubstr(cx);
+ RootedValue val(cx);
+
+ auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) {
+ singlePart = NewBuiltinClassInstance<PlainObject>(cx);
+ if (!singlePart)
+ return false;
+
+ partType = StringValue(cx->names().*type);
+ if (!DefineProperty(cx, singlePart, cx->names().type, partType))
+ return false;
+
+ partSubstr = SubstringKernel(cx, overallResult, beginIndex, endIndex - beginIndex);
+ if (!partSubstr)
+ return false;
+
+ val = StringValue(partSubstr);
+ if (!DefineProperty(cx, singlePart, cx->names().value, val))
+ return false;
+
+ val = ObjectValue(*singlePart);
+ if (!DefineElement(cx, partsArray, partIndex, val))
+ return false;
+
+ lastEndIndex = endIndex;
+ partIndex++;
+ return true;
+ };
+
+ int32_t fieldInt, beginIndexInt, endIndexInt;
+ while ((fieldInt = ufieldpositer_next(fpositer, &beginIndexInt, &endIndexInt)) >= 0) {
+ MOZ_ASSERT(beginIndexInt >= 0);
+ MOZ_ASSERT(endIndexInt >= 0);
+ MOZ_ASSERT(beginIndexInt <= endIndexInt,
+ "field iterator returning invalid range");
+
+ size_t beginIndex(beginIndexInt);
+ size_t endIndex(endIndexInt);
+
+ // Technically this isn't guaranteed. But it appears true in pratice,
+ // and http://bugs.icu-project.org/trac/ticket/12024 is expected to
+ // correct the documentation lapse.
+ MOZ_ASSERT(lastEndIndex <= beginIndex,
+ "field iteration didn't return fields in order start to "
+ "finish as expected");
+
+ if (FieldType type = GetFieldTypeForFormatField(static_cast<UDateFormatField>(fieldInt))) {
+ if (lastEndIndex < beginIndex) {
+ if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex))
+ return false;
+ }
+
+ if (!AppendPart(type, beginIndex, endIndex))
+ return false;
+ }
+ }
+
+ // Append any final literal.
+ if (lastEndIndex < overallResult->length()) {
+ if (!AppendPart(&JSAtomState::literal, lastEndIndex, overallResult->length()))
+ return false;
+ }
+
+ result.setObject(*partsArray);
+ return true;
+}
+
+bool
+js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 3);
+ MOZ_ASSERT(args[0].isObject());
+ MOZ_ASSERT(args[1].isNumber());
+ MOZ_ASSERT(args[2].isBoolean());
+
+ RootedObject dateTimeFormat(cx, &args[0].toObject());
+
+ // Obtain a UDateFormat object, cached if possible.
+ bool isDateTimeFormatInstance = dateTimeFormat->getClass() == &DateTimeFormatClass;
+ UDateFormat* df;
+ if (isDateTimeFormatInstance) {
+ void* priv =
+ dateTimeFormat->as<NativeObject>().getReservedSlot(UDATE_FORMAT_SLOT).toPrivate();
+ df = static_cast<UDateFormat*>(priv);
+ if (!df) {
+ df = NewUDateFormat(cx, dateTimeFormat);
+ if (!df)
+ return false;
+ dateTimeFormat->as<NativeObject>().setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(df));
+ }
+ } else {
+ // There's no good place to cache the ICU date-time format for an object
+ // that has been initialized as a DateTimeFormat but is not a
+ // DateTimeFormat instance. One possibility might be to add a
+ // DateTimeFormat instance as an internal property to each such object.
+ df = NewUDateFormat(cx, dateTimeFormat);
+ if (!df)
+ return false;
+ }
+
+ // Use the UDateFormat to actually format the time stamp.
+ RootedValue result(cx);
+ bool success = args[2].toBoolean()
+ ? intl_FormatToPartsDateTime(cx, df, args[1].toNumber(), &result)
+ : intl_FormatDateTime(cx, df, args[1].toNumber(), &result);
+
+ if (!isDateTimeFormatInstance)
+ udat_close(df);
+ if (!success)
+ return false;
+ args.rval().set(result);
+ return true;
+}
+
+bool
+js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+
+ JSAutoByteString locale(cx, args[0].toString());
+ if (!locale)
+ return false;
+
+ UErrorCode status = U_ZERO_ERROR;
+ const UChar* uTimeZone = nullptr;
+ int32_t uTimeZoneLength = 0;
+ UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, locale.ptr(), UCAL_DEFAULT, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+ ScopedICUObject<UCalendar, ucal_close> toClose(cal);
+
+ RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!info)
+ return false;
+
+ RootedValue v(cx);
+ int32_t firstDayOfWeek = ucal_getAttribute(cal, UCAL_FIRST_DAY_OF_WEEK);
+ v.setInt32(firstDayOfWeek);
+
+ if (!DefineProperty(cx, info, cx->names().firstDayOfWeek, v))
+ return false;
+
+ int32_t minDays = ucal_getAttribute(cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK);
+ v.setInt32(minDays);
+ if (!DefineProperty(cx, info, cx->names().minDays, v))
+ return false;
+
+ UCalendarWeekdayType prevDayType = ucal_getDayOfWeekType(cal, UCAL_SATURDAY, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ RootedValue weekendStart(cx), weekendEnd(cx);
+
+ for (int i = UCAL_SUNDAY; i <= UCAL_SATURDAY; i++) {
+ UCalendarDaysOfWeek dayOfWeek = static_cast<UCalendarDaysOfWeek>(i);
+ UCalendarWeekdayType type = ucal_getDayOfWeekType(cal, dayOfWeek, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ if (prevDayType != type) {
+ switch (type) {
+ case UCAL_WEEKDAY:
+ // If the first Weekday after Weekend is Sunday (1),
+ // then the last Weekend day is Saturday (7).
+ // Otherwise we'll just take the previous days number.
+ weekendEnd.setInt32(i == 1 ? 7 : i - 1);
+ break;
+ case UCAL_WEEKEND:
+ weekendStart.setInt32(i);
+ break;
+ case UCAL_WEEKEND_ONSET:
+ case UCAL_WEEKEND_CEASE:
+ // At the time this code was added, ICU apparently never behaves this way,
+ // so just throw, so that users will report a bug and we can decide what to
+ // do.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ default:
+ break;
+ }
+ }
+
+ prevDayType = type;
+ }
+
+ MOZ_ASSERT(weekendStart.isInt32());
+ MOZ_ASSERT(weekendEnd.isInt32());
+
+ if (!DefineProperty(cx, info, cx->names().weekendStart, weekendStart))
+ return false;
+
+ if (!DefineProperty(cx, info, cx->names().weekendEnd, weekendEnd))
+ return false;
+
+ args.rval().setObject(*info);
+ return true;
+}
+
+/******************** Intl ********************/
+
+const Class js::IntlClass = {
+ js_Object_str,
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Intl)
+};
+
+#if JS_HAS_TOSOURCE
+static bool
+intl_toSource(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setString(cx->names().Intl);
+ return true;
+}
+#endif
+
+static const JSFunctionSpec intl_static_methods[] = {
+#if JS_HAS_TOSOURCE
+ JS_FN(js_toSource_str, intl_toSource, 0, 0),
+#endif
+ JS_SELF_HOSTED_FN("getCanonicalLocales", "Intl_getCanonicalLocales", 1, 0),
+ JS_FS_END
+};
+
+/**
+ * Initializes the Intl Object and its standard built-in properties.
+ * Spec: ECMAScript Internationalization API Specification, 8.0, 8.1
+ */
+bool
+GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
+{
+ RootedObject proto(cx, global->getOrCreateObjectPrototype(cx));
+ if (!proto)
+ return false;
+
+ // The |Intl| object is just a plain object with some "static" function
+ // properties and some constructor properties.
+ RootedObject intl(cx, NewObjectWithGivenProto(cx, &IntlClass, proto, SingletonObject));
+ if (!intl)
+ return false;
+
+ // Add the static functions.
+ if (!JS_DefineFunctions(cx, intl, intl_static_methods))
+ return false;
+
+ // Add the constructor properties, computing and returning the relevant
+ // prototype objects needed below.
+ RootedObject collatorProto(cx, CreateCollatorPrototype(cx, intl, global));
+ if (!collatorProto)
+ return false;
+ RootedObject dateTimeFormatProto(cx, CreateDateTimeFormatPrototype(cx, intl, global));
+ if (!dateTimeFormatProto)
+ return false;
+ RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global));
+ if (!numberFormatProto)
+ return false;
+
+ // The |Intl| object is fully set up now, so define the global property.
+ RootedValue intlValue(cx, ObjectValue(*intl));
+ if (!DefineProperty(cx, global, cx->names().Intl, intlValue, nullptr, nullptr,
+ JSPROP_RESOLVING))
+ {
+ return false;
+ }
+
+ // Now that the |Intl| object is successfully added, we can OOM-safely fill
+ // in all relevant reserved global slots.
+
+ // Cache the various prototypes, for use in creating instances of these
+ // objects with the proper [[Prototype]] as "the original value of
+ // |Intl.Collator.prototype|" and similar. For builtin classes like
+ // |String.prototype| we have |JSProto_*| that enables
+ // |getPrototype(JSProto_*)|, but that has global-object-property-related
+ // baggage we don't need or want, so we use one-off reserved slots.
+ global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*collatorProto));
+ global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto));
+ global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
+
+ // Also cache |Intl| to implement spec language that conditions behavior
+ // based on values being equal to "the standard built-in |Intl| object".
+ // Use |setConstructor| to correspond with |JSProto_Intl|.
+ //
+ // XXX We should possibly do a one-off reserved slot like above.
+ global->setConstructor(JSProto_Intl, ObjectValue(*intl));
+ return true;
+}
+
+JSObject*
+js::InitIntlClass(JSContext* cx, HandleObject obj)
+{
+ Handle<GlobalObject*> global = obj.as<GlobalObject>();
+ if (!GlobalObject::initIntlObject(cx, global))
+ return nullptr;
+
+ return &global->getConstructor(JSProto_Intl).toObject();
+}
diff --git a/js/src/builtin/Intl.h b/js/src/builtin/Intl.h
new file mode 100644
index 000000000..54764605b
--- /dev/null
+++ b/js/src/builtin/Intl.h
@@ -0,0 +1,409 @@
+/* -*- 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/. */
+
+#ifndef builtin_Intl_h
+#define builtin_Intl_h
+
+#include "mozilla/HashFunctions.h"
+#include "mozilla/MemoryReporting.h"
+
+#include "jsalloc.h"
+#include "NamespaceImports.h"
+
+#include "js/GCAPI.h"
+#include "js/GCHashTable.h"
+
+#if ENABLE_INTL_API
+#include "unicode/utypes.h"
+#endif
+
+/*
+ * The Intl module specified by standard ECMA-402,
+ * ECMAScript Internationalization API Specification.
+ */
+
+namespace js {
+
+/**
+ * Initializes the Intl Object and its standard built-in properties.
+ * Spec: ECMAScript Internationalization API Specification, 8.0, 8.1
+ */
+extern JSObject*
+InitIntlClass(JSContext* cx, HandleObject obj);
+
+/**
+ * Stores Intl data which can be shared across compartments (but not contexts).
+ *
+ * Used for data which is expensive when computed repeatedly or is not
+ * available through ICU.
+ */
+class SharedIntlData
+{
+ /**
+ * Information tracking the set of the supported time zone names, derived
+ * from the IANA time zone database <https://www.iana.org/time-zones>.
+ *
+ * There are two kinds of IANA time zone names: Zone and Link (denoted as
+ * such in database source files). Zone names are the canonical, preferred
+ * name for a time zone, e.g. Asia/Kolkata. Link names simply refer to
+ * target Zone names for their meaning, e.g. Asia/Calcutta targets
+ * Asia/Kolkata. That a name is a Link doesn't *necessarily* reflect a
+ * sense of deprecation: some Link names also exist partly for convenience,
+ * e.g. UTC and GMT as Link names targeting the Zone name Etc/UTC.
+ *
+ * Two data sources determine the time zone names we support: those ICU
+ * supports and IANA's zone information.
+ *
+ * Unfortunately the names ICU and IANA support, and their Link
+ * relationships from name to target, aren't identical, so we can't simply
+ * implicitly trust ICU's name handling. We must perform various
+ * preprocessing of user-provided zone names and post-processing of
+ * ICU-provided zone names to implement ECMA-402's IANA-consistent behavior.
+ *
+ * Also see <https://ssl.icu-project.org/trac/ticket/12044> and
+ * <http://unicode.org/cldr/trac/ticket/9892>.
+ */
+
+ using TimeZoneName = JSAtom*;
+
+ struct TimeZoneHasher
+ {
+ struct Lookup
+ {
+ union {
+ const JS::Latin1Char* latin1Chars;
+ const char16_t* twoByteChars;
+ };
+ bool isLatin1;
+ size_t length;
+ JS::AutoCheckCannotGC nogc;
+ HashNumber hash;
+
+ explicit Lookup(JSFlatString* timeZone);
+ };
+
+ static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
+ static bool match(TimeZoneName key, const Lookup& lookup);
+ };
+
+ using TimeZoneSet = js::GCHashSet<TimeZoneName,
+ TimeZoneHasher,
+ js::SystemAllocPolicy>;
+
+ using TimeZoneMap = js::GCHashMap<TimeZoneName,
+ TimeZoneName,
+ TimeZoneHasher,
+ js::SystemAllocPolicy>;
+
+ /**
+ * As a threshold matter, available time zones are those time zones ICU
+ * supports, via ucal_openTimeZones. But ICU supports additional non-IANA
+ * time zones described in intl/icu/source/tools/tzcode/icuzones (listed in
+ * IntlTimeZoneData.cpp's |legacyICUTimeZones|) for its own backwards
+ * compatibility purposes. This set consists of ICU's supported time zones,
+ * minus all backwards-compatibility time zones.
+ */
+ TimeZoneSet availableTimeZones;
+
+ /**
+ * IANA treats some time zone names as Zones, that ICU instead treats as
+ * Links. For example, IANA considers "America/Indiana/Indianapolis" to be
+ * a Zone and "America/Fort_Wayne" a Link that targets it, but ICU
+ * considers the former a Link that targets "America/Indianapolis" (which
+ * IANA treats as a Link).
+ *
+ * ECMA-402 requires that we respect IANA data, so if we're asked to
+ * canonicalize a time zone name in this set, we must *not* return ICU's
+ * canonicalization.
+ */
+ TimeZoneSet ianaZonesTreatedAsLinksByICU;
+
+ /**
+ * IANA treats some time zone names as Links to one target, that ICU
+ * instead treats as either Zones, or Links to different targets. An
+ * example of the former is "Asia/Calcutta, which IANA assigns the target
+ * "Asia/Kolkata" but ICU considers its own Zone. An example of the latter
+ * is "America/Virgin", which IANA assigns the target
+ * "America/Port_of_Spain" but ICU assigns the target "America/St_Thomas".
+ *
+ * ECMA-402 requires that we respect IANA data, so if we're asked to
+ * canonicalize a time zone name that's a key in this map, we *must* return
+ * the corresponding value and *must not* return ICU's canonicalization.
+ */
+ TimeZoneMap ianaLinksCanonicalizedDifferentlyByICU;
+
+ bool timeZoneDataInitialized = false;
+
+ /**
+ * Precomputes the available time zone names, because it's too expensive to
+ * call ucal_openTimeZones() repeatedly.
+ */
+ bool ensureTimeZones(JSContext* cx);
+
+ public:
+ /**
+ * Returns the validated time zone name in |result|. If the input time zone
+ * isn't a valid IANA time zone name, |result| remains unchanged.
+ */
+ bool validateTimeZoneName(JSContext* cx, JS::HandleString timeZone,
+ JS::MutableHandleString result);
+
+ /**
+ * Returns the canonical time zone name in |result|. If no canonical name
+ * was found, |result| remains unchanged.
+ *
+ * This method only handles time zones which are canonicalized differently
+ * by ICU when compared to IANA.
+ */
+ bool tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, JS::HandleString timeZone,
+ JS::MutableHandleString result);
+
+ void destroyInstance();
+
+ void trace(JSTracer* trc);
+
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+};
+
+/*
+ * The following functions are for use by self-hosted code.
+ */
+
+
+/******************** Collator ********************/
+
+/**
+ * Returns a new instance of the standard built-in Collator constructor.
+ * Self-hosted code cannot cache this constructor (as it does for others in
+ * Utilities.js) because it is initialized after self-hosted code is compiled.
+ *
+ * Usage: collator = intl_Collator(locales, options)
+ */
+extern MOZ_MUST_USE bool
+intl_Collator(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Returns an object indicating the supported locales for collation
+ * by having a true-valued property for each such locale with the
+ * canonicalized language tag as the property name. The object has no
+ * prototype.
+ *
+ * Usage: availableLocales = intl_Collator_availableLocales()
+ */
+extern MOZ_MUST_USE bool
+intl_Collator_availableLocales(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Returns an array with the collation type identifiers per Unicode
+ * Technical Standard 35, Unicode Locale Data Markup Language, for the
+ * collations supported for the given locale. "standard" and "search" are
+ * excluded.
+ *
+ * Usage: collations = intl_availableCollations(locale)
+ */
+extern MOZ_MUST_USE bool
+intl_availableCollations(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Compares x and y (which must be String values), and returns a number less
+ * than 0 if x < y, 0 if x = y, or a number greater than 0 if x > y according
+ * to the sort order for the locale and collation options of the given
+ * Collator.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 10.3.2.
+ *
+ * Usage: result = intl_CompareStrings(collator, x, y)
+ */
+extern MOZ_MUST_USE bool
+intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp);
+
+
+/******************** NumberFormat ********************/
+
+/**
+ * Returns a new instance of the standard built-in NumberFormat constructor.
+ * Self-hosted code cannot cache this constructor (as it does for others in
+ * Utilities.js) because it is initialized after self-hosted code is compiled.
+ *
+ * Usage: numberFormat = intl_NumberFormat(locales, options)
+ */
+extern MOZ_MUST_USE bool
+intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Returns an object indicating the supported locales for number formatting
+ * by having a true-valued property for each such locale with the
+ * canonicalized language tag as the property name. The object has no
+ * prototype.
+ *
+ * Usage: availableLocales = intl_NumberFormat_availableLocales()
+ */
+extern MOZ_MUST_USE bool
+intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Returns the numbering system type identifier per Unicode
+ * Technical Standard 35, Unicode Locale Data Markup Language, for the
+ * default numbering system for the given locale.
+ *
+ * Usage: defaultNumberingSystem = intl_numberingSystem(locale)
+ */
+extern MOZ_MUST_USE bool
+intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Returns a string representing the number x according to the effective
+ * locale and the formatting options of the given NumberFormat.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 11.3.2.
+ *
+ * Usage: formatted = intl_FormatNumber(numberFormat, x)
+ */
+extern MOZ_MUST_USE bool
+intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp);
+
+
+/******************** DateTimeFormat ********************/
+
+/**
+ * Returns a new instance of the standard built-in DateTimeFormat constructor.
+ * Self-hosted code cannot cache this constructor (as it does for others in
+ * Utilities.js) because it is initialized after self-hosted code is compiled.
+ *
+ * Usage: dateTimeFormat = intl_DateTimeFormat(locales, options)
+ */
+extern MOZ_MUST_USE bool
+intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Returns an object indicating the supported locales for date and time
+ * formatting by having a true-valued property for each such locale with the
+ * canonicalized language tag as the property name. The object has no
+ * prototype.
+ *
+ * Usage: availableLocales = intl_DateTimeFormat_availableLocales()
+ */
+extern MOZ_MUST_USE bool
+intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Returns an array with the calendar type identifiers per Unicode
+ * Technical Standard 35, Unicode Locale Data Markup Language, for the
+ * supported calendars for the given locale. The default calendar is
+ * element 0.
+ *
+ * Usage: calendars = intl_availableCalendars(locale)
+ */
+extern MOZ_MUST_USE bool
+intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * 6.4.1 IsValidTimeZoneName ( timeZone )
+ *
+ * Verifies that the given string is a valid time zone name. If it is a valid
+ * time zone name, its IANA time zone name is returned. Otherwise returns null.
+ *
+ * ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3
+ *
+ * Usage: ianaTimeZone = intl_IsValidTimeZoneName(timeZone)
+ */
+extern MOZ_MUST_USE bool
+intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Return the canonicalized time zone name. Canonicalization resolves link
+ * names to their target time zones.
+ *
+ * Usage: ianaTimeZone = intl_canonicalizeTimeZone(timeZone)
+ */
+extern MOZ_MUST_USE bool
+intl_canonicalizeTimeZone(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Return the default time zone name. The time zone name is not canonicalized.
+ *
+ * Usage: icuDefaultTimeZone = intl_defaultTimeZone()
+ */
+extern MOZ_MUST_USE bool
+intl_defaultTimeZone(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Return the raw offset from GMT in milliseconds for the default time zone.
+ *
+ * Usage: defaultTimeZoneOffset = intl_defaultTimeZoneOffset()
+ */
+extern MOZ_MUST_USE bool
+intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Return a pattern in the date-time format pattern language of Unicode
+ * Technical Standard 35, Unicode Locale Data Markup Language, for the
+ * best-fit date-time format pattern corresponding to skeleton for the
+ * given locale.
+ *
+ * Usage: pattern = intl_patternForSkeleton(locale, skeleton)
+ */
+extern MOZ_MUST_USE bool
+intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Returns a String value representing x (which must be a Number value)
+ * according to the effective locale and the formatting options of the
+ * given DateTimeFormat.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 12.3.2.
+ *
+ * Usage: formatted = intl_FormatDateTime(dateTimeFormat, x)
+ */
+extern MOZ_MUST_USE bool
+intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Returns a plain object with calendar information for a single valid locale
+ * (callers must perform this validation). The object will have these
+ * properties:
+ *
+ * firstDayOfWeek
+ * an integer in the range 1=Sunday to 7=Saturday indicating the day
+ * considered the first day of the week in calendars, e.g. 1 for en-US,
+ * 2 for en-GB, 1 for bn-IN
+ * minDays
+ * an integer in the range of 1 to 7 indicating the minimum number
+ * of days required in the first week of the year, e.g. 1 for en-US, 4 for de
+ * weekendStart
+ * an integer in the range 1=Sunday to 7=Saturday indicating the day
+ * considered the beginning of a weekend, e.g. 7 for en-US, 7 for en-GB,
+ * 1 for bn-IN
+ * weekendEnd
+ * an integer in the range 1=Sunday to 7=Saturday indicating the day
+ * considered the end of a weekend, e.g. 1 for en-US, 1 for en-GB,
+ * 1 for bn-IN (note that "weekend" is *not* necessarily two days)
+ *
+ * NOTE: "calendar" and "locale" properties are *not* added to the object.
+ */
+extern MOZ_MUST_USE bool
+intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp);
+
+#if ENABLE_INTL_API
+/**
+ * Cast char16_t* strings to UChar* strings used by ICU.
+ */
+inline const UChar*
+Char16ToUChar(const char16_t* chars)
+{
+ return reinterpret_cast<const UChar*>(chars);
+}
+
+inline UChar*
+Char16ToUChar(char16_t* chars)
+{
+ return reinterpret_cast<UChar*>(chars);
+}
+#endif // ENABLE_INTL_API
+
+} // namespace js
+
+#endif /* builtin_Intl_h */
diff --git a/js/src/builtin/Intl.js b/js/src/builtin/Intl.js
new file mode 100644
index 000000000..493062c1c
--- /dev/null
+++ b/js/src/builtin/Intl.js
@@ -0,0 +1,3008 @@
+/* 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/. */
+
+/* Portions Copyright Norbert Lindenberg 2011-2012. */
+
+/*global JSMSG_INTL_OBJECT_NOT_INITED: false, JSMSG_INVALID_LOCALES_ELEMENT: false,
+ JSMSG_INVALID_LANGUAGE_TAG: false, JSMSG_INVALID_LOCALE_MATCHER: false,
+ JSMSG_INVALID_OPTION_VALUE: false, JSMSG_INVALID_DIGITS_VALUE: false,
+ JSMSG_INTL_OBJECT_REINITED: false, JSMSG_INVALID_CURRENCY_CODE: false,
+ JSMSG_UNDEFINED_CURRENCY: false, JSMSG_INVALID_TIME_ZONE: false,
+ JSMSG_DATE_NOT_FINITE: false,
+ intl_Collator_availableLocales: false,
+ intl_availableCollations: false,
+ intl_CompareStrings: false,
+ intl_NumberFormat_availableLocales: false,
+ intl_numberingSystem: false,
+ intl_FormatNumber: false,
+ intl_DateTimeFormat_availableLocales: false,
+ intl_availableCalendars: false,
+ intl_patternForSkeleton: false,
+ intl_FormatDateTime: false,
+*/
+
+/*
+ * The Intl module specified by standard ECMA-402,
+ * ECMAScript Internationalization API Specification.
+ */
+
+
+/********** Locales, Time Zones, and Currencies **********/
+
+
+/**
+ * Convert s to upper case, but limited to characters a-z.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 6.1.
+ */
+function toASCIIUpperCase(s) {
+ assert(typeof s === "string", "toASCIIUpperCase");
+
+ // String.prototype.toUpperCase may map non-ASCII characters into ASCII,
+ // so go character by character (actually code unit by code unit, but
+ // since we only care about ASCII characters here, that's OK).
+ var result = "";
+ for (var i = 0; i < s.length; i++) {
+ var c = callFunction(std_String_charCodeAt, s, i);
+ result += (0x61 <= c && c <= 0x7A)
+ ? callFunction(std_String_fromCharCode, null, c & ~0x20)
+ : s[i];
+ }
+ return result;
+}
+
+/**
+ * Holder object for encapsulating regexp instances.
+ *
+ * Regular expression instances should be created after the initialization of
+ * self-hosted global.
+ */
+var internalIntlRegExps = std_Object_create(null);
+internalIntlRegExps.unicodeLocaleExtensionSequenceRE = null;
+internalIntlRegExps.languageTagRE = null;
+internalIntlRegExps.duplicateVariantRE = null;
+internalIntlRegExps.duplicateSingletonRE = null;
+internalIntlRegExps.isWellFormedCurrencyCodeRE = null;
+internalIntlRegExps.currencyDigitsRE = null;
+
+/**
+ * Regular expression matching a "Unicode locale extension sequence", which the
+ * specification defines as: "any substring of a language tag that starts with
+ * a separator '-' and the singleton 'u' and includes the maximum sequence of
+ * following non-singleton subtags and their preceding '-' separators."
+ *
+ * Alternatively, this may be defined as: the components of a language tag that
+ * match the extension production in RFC 5646, where the singleton component is
+ * "u".
+ *
+ * Spec: ECMAScript Internationalization API Specification, 6.2.1.
+ */
+function getUnicodeLocaleExtensionSequenceRE() {
+ return internalIntlRegExps.unicodeLocaleExtensionSequenceRE ||
+ (internalIntlRegExps.unicodeLocaleExtensionSequenceRE =
+ RegExpCreate("-u(?:-[a-z0-9]{2,8})+"));
+}
+
+
+/**
+ * Removes Unicode locale extension sequences from the given language tag.
+ */
+function removeUnicodeExtensions(locale) {
+ // A wholly-privateuse locale has no extension sequences.
+ if (callFunction(std_String_startsWith, locale, "x-"))
+ return locale;
+
+ // Otherwise, split on "-x-" marking the start of any privateuse component.
+ // Replace Unicode locale extension sequences in the left half, and return
+ // the concatenation.
+ var pos = callFunction(std_String_indexOf, locale, "-x-");
+ if (pos < 0)
+ pos = locale.length;
+
+ var left = callFunction(String_substring, locale, 0, pos);
+ var right = callFunction(String_substring, locale, pos);
+
+ var extensions;
+ var unicodeLocaleExtensionSequenceRE = getUnicodeLocaleExtensionSequenceRE();
+ while ((extensions = regexp_exec_no_statics(unicodeLocaleExtensionSequenceRE, left)) !== null) {
+ left = StringReplaceString(left, extensions[0], "");
+ unicodeLocaleExtensionSequenceRE.lastIndex = 0;
+ }
+
+ var combined = left + right;
+ assert(IsStructurallyValidLanguageTag(combined), "recombination produced an invalid language tag");
+ assert(function() {
+ var uindex = callFunction(std_String_indexOf, combined, "-u-");
+ if (uindex < 0)
+ return true;
+ var xindex = callFunction(std_String_indexOf, combined, "-x-");
+ return xindex > 0 && xindex < uindex;
+ }(), "recombination failed to remove all Unicode locale extension sequences");
+
+ return combined;
+}
+
+
+/**
+ * Regular expression defining BCP 47 language tags.
+ *
+ * Spec: RFC 5646 section 2.1.
+ */
+function getLanguageTagRE() {
+ if (internalIntlRegExps.languageTagRE)
+ return internalIntlRegExps.languageTagRE;
+
+ // RFC 5234 section B.1
+ // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
+ var ALPHA = "[a-zA-Z]";
+ // DIGIT = %x30-39
+ // ; 0-9
+ var DIGIT = "[0-9]";
+
+ // RFC 5646 section 2.1
+ // alphanum = (ALPHA / DIGIT) ; letters and numbers
+ var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")";
+ // regular = "art-lojban" ; these tags match the 'langtag'
+ // / "cel-gaulish" ; production, but their subtags
+ // / "no-bok" ; are not extended language
+ // / "no-nyn" ; or variant subtags: their meaning
+ // / "zh-guoyu" ; is defined by their registration
+ // / "zh-hakka" ; and all of these are deprecated
+ // / "zh-min" ; in favor of a more modern
+ // / "zh-min-nan" ; subtag or sequence of subtags
+ // / "zh-xiang"
+ var regular = "(?:art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)";
+ // irregular = "en-GB-oed" ; irregular tags do not match
+ // / "i-ami" ; the 'langtag' production and
+ // / "i-bnn" ; would not otherwise be
+ // / "i-default" ; considered 'well-formed'
+ // / "i-enochian" ; These tags are all valid,
+ // / "i-hak" ; but most are deprecated
+ // / "i-klingon" ; in favor of more modern
+ // / "i-lux" ; subtags or subtag
+ // / "i-mingo" ; combination
+ // / "i-navajo"
+ // / "i-pwn"
+ // / "i-tao"
+ // / "i-tay"
+ // / "i-tsu"
+ // / "sgn-BE-FR"
+ // / "sgn-BE-NL"
+ // / "sgn-CH-DE"
+ var irregular = "(?:en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)";
+ // grandfathered = irregular ; non-redundant tags registered
+ // / regular ; during the RFC 3066 era
+ var grandfathered = "(?:" + irregular + "|" + regular + ")";
+ // privateuse = "x" 1*("-" (1*8alphanum))
+ var privateuse = "(?:x(?:-[a-z0-9]{1,8})+)";
+ // singleton = DIGIT ; 0 - 9
+ // / %x41-57 ; A - W
+ // / %x59-5A ; Y - Z
+ // / %x61-77 ; a - w
+ // / %x79-7A ; y - z
+ var singleton = "(?:" + DIGIT + "|[A-WY-Za-wy-z])";
+ // extension = singleton 1*("-" (2*8alphanum))
+ var extension = "(?:" + singleton + "(?:-" + alphanum + "{2,8})+)";
+ // variant = 5*8alphanum ; registered variants
+ // / (DIGIT 3alphanum)
+ var variant = "(?:" + alphanum + "{5,8}|(?:" + DIGIT + alphanum + "{3}))";
+ // region = 2ALPHA ; ISO 3166-1 code
+ // / 3DIGIT ; UN M.49 code
+ var region = "(?:" + ALPHA + "{2}|" + DIGIT + "{3})";
+ // script = 4ALPHA ; ISO 15924 code
+ var script = "(?:" + ALPHA + "{4})";
+ // extlang = 3ALPHA ; selected ISO 639 codes
+ // *2("-" 3ALPHA) ; permanently reserved
+ var extlang = "(?:" + ALPHA + "{3}(?:-" + ALPHA + "{3}){0,2})";
+ // language = 2*3ALPHA ; shortest ISO 639 code
+ // ["-" extlang] ; sometimes followed by
+ // ; extended language subtags
+ // / 4ALPHA ; or reserved for future use
+ // / 5*8ALPHA ; or registered language subtag
+ var language = "(?:" + ALPHA + "{2,3}(?:-" + extlang + ")?|" + ALPHA + "{4}|" + ALPHA + "{5,8})";
+ // langtag = language
+ // ["-" script]
+ // ["-" region]
+ // *("-" variant)
+ // *("-" extension)
+ // ["-" privateuse]
+ var langtag = language + "(?:-" + script + ")?(?:-" + region + ")?(?:-" +
+ variant + ")*(?:-" + extension + ")*(?:-" + privateuse + ")?";
+ // Language-Tag = langtag ; normal language tags
+ // / privateuse ; private use tag
+ // / grandfathered ; grandfathered tags
+ var languageTag = "^(?:" + langtag + "|" + privateuse + "|" + grandfathered + ")$";
+
+ // Language tags are case insensitive (RFC 5646 section 2.1.1).
+ return (internalIntlRegExps.languageTagRE = RegExpCreate(languageTag, "i"));
+}
+
+
+function getDuplicateVariantRE() {
+ if (internalIntlRegExps.duplicateVariantRE)
+ return internalIntlRegExps.duplicateVariantRE;
+
+ // RFC 5234 section B.1
+ // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
+ var ALPHA = "[a-zA-Z]";
+ // DIGIT = %x30-39
+ // ; 0-9
+ var DIGIT = "[0-9]";
+
+ // RFC 5646 section 2.1
+ // alphanum = (ALPHA / DIGIT) ; letters and numbers
+ var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")";
+ // variant = 5*8alphanum ; registered variants
+ // / (DIGIT 3alphanum)
+ var variant = "(?:" + alphanum + "{5,8}|(?:" + DIGIT + alphanum + "{3}))";
+
+ // Match a langtag that contains a duplicate variant.
+ var duplicateVariant =
+ // Match everything in a langtag prior to any variants, and maybe some
+ // of the variants as well (which makes this pattern inefficient but
+ // not wrong, for our purposes);
+ "(?:" + alphanum + "{2,8}-)+" +
+ // a variant, parenthesised so that we can refer back to it later;
+ "(" + variant + ")-" +
+ // zero or more subtags at least two characters long (thus stopping
+ // before extension and privateuse components);
+ "(?:" + alphanum + "{2,8}-)*" +
+ // and the same variant again
+ "\\1" +
+ // ...but not followed by any characters that would turn it into a
+ // different subtag.
+ "(?!" + alphanum + ")";
+
+ // Language tags are case insensitive (RFC 5646 section 2.1.1). Using
+ // character classes covering both upper- and lower-case characters nearly
+ // addresses this -- but for the possibility of variant repetition with
+ // differing case, e.g. "en-variant-Variant". Use a case-insensitive
+ // regular expression to address this. (Note that there's no worry about
+ // case transformation accepting invalid characters here: users have
+ // already verified the string is alphanumeric Latin plus "-".)
+ return (internalIntlRegExps.duplicateVariantRE = RegExpCreate(duplicateVariant, "i"));
+}
+
+
+function getDuplicateSingletonRE() {
+ if (internalIntlRegExps.duplicateSingletonRE)
+ return internalIntlRegExps.duplicateSingletonRE;
+
+ // RFC 5234 section B.1
+ // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
+ var ALPHA = "[a-zA-Z]";
+ // DIGIT = %x30-39
+ // ; 0-9
+ var DIGIT = "[0-9]";
+
+ // RFC 5646 section 2.1
+ // alphanum = (ALPHA / DIGIT) ; letters and numbers
+ var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")";
+ // singleton = DIGIT ; 0 - 9
+ // / %x41-57 ; A - W
+ // / %x59-5A ; Y - Z
+ // / %x61-77 ; a - w
+ // / %x79-7A ; y - z
+ var singleton = "(?:" + DIGIT + "|[A-WY-Za-wy-z])";
+
+ // Match a langtag that contains a duplicate singleton.
+ var duplicateSingleton =
+ // Match a singleton subtag, parenthesised so that we can refer back to
+ // it later;
+ "-(" + singleton + ")-" +
+ // then zero or more subtags;
+ "(?:" + alphanum + "+-)*" +
+ // and the same singleton again
+ "\\1" +
+ // ...but not followed by any characters that would turn it into a
+ // different subtag.
+ "(?!" + alphanum + ")";
+
+ // Language tags are case insensitive (RFC 5646 section 2.1.1). Using
+ // character classes covering both upper- and lower-case characters nearly
+ // addresses this -- but for the possibility of singleton repetition with
+ // differing case, e.g. "en-u-foo-U-foo". Use a case-insensitive regular
+ // expression to address this. (Note that there's no worry about case
+ // transformation accepting invalid characters here: users have already
+ // verified the string is alphanumeric Latin plus "-".)
+ return (internalIntlRegExps.duplicateSingletonRE = RegExpCreate(duplicateSingleton, "i"));
+}
+
+
+/**
+ * Verifies that the given string is a well-formed BCP 47 language tag
+ * with no duplicate variant or singleton subtags.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 6.2.2.
+ */
+function IsStructurallyValidLanguageTag(locale) {
+ assert(typeof locale === "string", "IsStructurallyValidLanguageTag");
+ var languageTagRE = getLanguageTagRE();
+ if (!regexp_test_no_statics(languageTagRE, locale))
+ return false;
+
+ // Before checking for duplicate variant or singleton subtags with
+ // regular expressions, we have to get private use subtag sequences
+ // out of the picture.
+ if (callFunction(std_String_startsWith, locale, "x-"))
+ return true;
+ var pos = callFunction(std_String_indexOf, locale, "-x-");
+ if (pos !== -1)
+ locale = callFunction(String_substring, locale, 0, pos);
+
+ // Check for duplicate variant or singleton subtags.
+ var duplicateVariantRE = getDuplicateVariantRE();
+ var duplicateSingletonRE = getDuplicateSingletonRE();
+ return !regexp_test_no_statics(duplicateVariantRE, locale) &&
+ !regexp_test_no_statics(duplicateSingletonRE, locale);
+}
+
+
+/**
+ * Canonicalizes the given structurally valid BCP 47 language tag, including
+ * regularized case of subtags. For example, the language tag
+ * Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE, where
+ *
+ * Zh ; 2*3ALPHA
+ * -NAN ; ["-" extlang]
+ * -haNS ; ["-" script]
+ * -bu ; ["-" region]
+ * -variant2 ; *("-" variant)
+ * -Variant1
+ * -u-ca-chinese ; *("-" extension)
+ * -t-Zh-laTN
+ * -x-PRIVATE ; ["-" privateuse]
+ *
+ * becomes nan-Hans-mm-variant2-variant1-t-zh-latn-u-ca-chinese-x-private
+ *
+ * Spec: ECMAScript Internationalization API Specification, 6.2.3.
+ * Spec: RFC 5646, section 4.5.
+ */
+function CanonicalizeLanguageTag(locale) {
+ assert(IsStructurallyValidLanguageTag(locale), "CanonicalizeLanguageTag");
+
+ // The input
+ // "Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE"
+ // will be used throughout this method to illustrate how it works.
+
+ // Language tags are compared and processed case-insensitively, so
+ // technically it's not necessary to adjust case. But for easier processing,
+ // and because the canonical form for most subtags is lower case, we start
+ // with lower case for all.
+ // "Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE" ->
+ // "zh-nan-hans-bu-variant2-variant1-u-ca-chinese-t-zh-latn-x-private"
+ locale = callFunction(std_String_toLowerCase, locale);
+
+ // Handle mappings for complete tags.
+ if (callFunction(std_Object_hasOwnProperty, langTagMappings, locale))
+ return langTagMappings[locale];
+
+ var subtags = StringSplitString(ToString(locale), "-");
+ var i = 0;
+
+ // Handle the standard part: All subtags before the first singleton or "x".
+ // "zh-nan-hans-bu-variant2-variant1"
+ while (i < subtags.length) {
+ var subtag = subtags[i];
+
+ // If we reach the start of an extension sequence or private use part,
+ // we're done with this loop. We have to check for i > 0 because for
+ // irregular language tags, such as i-klingon, the single-character
+ // subtag "i" is not the start of an extension sequence.
+ // In the example, we break at "u".
+ if (subtag.length === 1 && (i > 0 || subtag === "x"))
+ break;
+
+ if (i !== 0) {
+ if (subtag.length === 4) {
+ // 4-character subtags that are not in initial position are
+ // script codes; their first character needs to be capitalized.
+ // "hans" -> "Hans"
+ subtag = callFunction(std_String_toUpperCase, subtag[0]) +
+ callFunction(String_substring, subtag, 1);
+ } else if (subtag.length === 2) {
+ // 2-character subtags that are not in initial position are
+ // region codes; they need to be upper case. "bu" -> "BU"
+ subtag = callFunction(std_String_toUpperCase, subtag);
+ }
+ }
+ if (callFunction(std_Object_hasOwnProperty, langSubtagMappings, subtag)) {
+ // Replace deprecated subtags with their preferred values.
+ // "BU" -> "MM"
+ // This has to come after we capitalize region codes because
+ // otherwise some language and region codes could be confused.
+ // For example, "in" is an obsolete language code for Indonesian,
+ // but "IN" is the country code for India.
+ // Note that the script generating langSubtagMappings makes sure
+ // that no regular subtag mapping will replace an extlang code.
+ subtag = langSubtagMappings[subtag];
+ } else if (callFunction(std_Object_hasOwnProperty, extlangMappings, subtag)) {
+ // Replace deprecated extlang subtags with their preferred values,
+ // and remove the preceding subtag if it's a redundant prefix.
+ // "zh-nan" -> "nan"
+ // Note that the script generating extlangMappings makes sure that
+ // no extlang mapping will replace a normal language code.
+ subtag = extlangMappings[subtag].preferred;
+ if (i === 1 && extlangMappings[subtag].prefix === subtags[0]) {
+ callFunction(std_Array_shift, subtags);
+ i--;
+ }
+ }
+ subtags[i] = subtag;
+ i++;
+ }
+ var normal = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, 0, i), "-");
+
+ // Extension sequences are sorted by their singleton characters.
+ // "u-ca-chinese-t-zh-latn" -> "t-zh-latn-u-ca-chinese"
+ var extensions = new List();
+ while (i < subtags.length && subtags[i] !== "x") {
+ var extensionStart = i;
+ i++;
+ while (i < subtags.length && subtags[i].length > 1)
+ i++;
+ var extension = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, extensionStart, i), "-");
+ callFunction(std_Array_push, extensions, extension);
+ }
+ callFunction(std_Array_sort, extensions);
+
+ // Private use sequences are left as is. "x-private"
+ var privateUse = "";
+ if (i < subtags.length)
+ privateUse = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, i), "-");
+
+ // Put everything back together.
+ var canonical = normal;
+ if (extensions.length > 0)
+ canonical += "-" + callFunction(std_Array_join, extensions, "-");
+ if (privateUse.length > 0) {
+ // Be careful of a Language-Tag that is entirely privateuse.
+ if (canonical.length > 0)
+ canonical += "-" + privateUse;
+ else
+ canonical = privateUse;
+ }
+
+ return canonical;
+}
+
+
+function localeContainsNoUnicodeExtensions(locale) {
+ // No "-u-", no possible Unicode extension.
+ if (callFunction(std_String_indexOf, locale, "-u-") === -1)
+ return true;
+
+ // "-u-" within privateuse also isn't one.
+ if (callFunction(std_String_indexOf, locale, "-u-") > callFunction(std_String_indexOf, locale, "-x-"))
+ return true;
+
+ // An entirely-privateuse tag doesn't contain extensions.
+ if (callFunction(std_String_startsWith, locale, "x-"))
+ return true;
+
+ // Otherwise, we have a Unicode extension sequence.
+ return false;
+}
+
+
+// The last-ditch locale is used if none of the available locales satisfies a
+// request. "en-GB" is used based on the assumptions that English is the most
+// common second language, that both en-GB and en-US are normally available in
+// an implementation, and that en-GB is more representative of the English used
+// in other locales.
+function lastDitchLocale() {
+ // Per bug 1177929, strings don't clone out of self-hosted code as atoms,
+ // breaking IonBuilder::constant. Put this in a function for now.
+ return "en-GB";
+}
+
+
+// Certain old, commonly-used language tags that lack a script, are expected to
+// nonetheless imply one. This object maps these old-style tags to modern
+// equivalents.
+var oldStyleLanguageTagMappings = {
+ "pa-PK": "pa-Arab-PK",
+ "zh-CN": "zh-Hans-CN",
+ "zh-HK": "zh-Hant-HK",
+ "zh-SG": "zh-Hans-SG",
+ "zh-TW": "zh-Hant-TW",
+};
+
+
+var localeCandidateCache = {
+ runtimeDefaultLocale: undefined,
+ candidateDefaultLocale: undefined,
+};
+
+
+var localeCache = {
+ runtimeDefaultLocale: undefined,
+ defaultLocale: undefined,
+};
+
+
+/**
+ * Compute the candidate default locale: the locale *requested* to be used as
+ * the default locale. We'll use it if and only if ICU provides support (maybe
+ * fallback support, e.g. supporting "de-ZA" through "de" support implied by a
+ * "de-DE" locale).
+ */
+function DefaultLocaleIgnoringAvailableLocales() {
+ const runtimeDefaultLocale = RuntimeDefaultLocale();
+ if (runtimeDefaultLocale === localeCandidateCache.runtimeDefaultLocale)
+ return localeCandidateCache.candidateDefaultLocale;
+
+ // If we didn't get a cache hit, compute the candidate default locale and
+ // cache it. Fall back on the last-ditch locale when necessary.
+ var candidate;
+ if (!IsStructurallyValidLanguageTag(runtimeDefaultLocale)) {
+ candidate = lastDitchLocale();
+ } else {
+ candidate = CanonicalizeLanguageTag(runtimeDefaultLocale);
+
+ // The default locale must be in [[availableLocales]], and that list
+ // must not contain any locales with Unicode extension sequences, so
+ // remove any present in the candidate.
+ candidate = removeUnicodeExtensions(candidate);
+
+ if (callFunction(std_Object_hasOwnProperty, oldStyleLanguageTagMappings, candidate))
+ candidate = oldStyleLanguageTagMappings[candidate];
+ }
+
+ // Cache the candidate locale until the runtime default locale changes.
+ localeCandidateCache.candidateDefaultLocale = candidate;
+ localeCandidateCache.runtimeDefaultLocale = runtimeDefaultLocale;
+
+ assert(IsStructurallyValidLanguageTag(candidate),
+ "the candidate must be structurally valid");
+ assert(localeContainsNoUnicodeExtensions(candidate),
+ "the candidate must not contain a Unicode extension sequence");
+
+ return candidate;
+}
+
+
+/**
+ * Returns the BCP 47 language tag for the host environment's current locale.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 6.2.4.
+ */
+function DefaultLocale() {
+ const runtimeDefaultLocale = RuntimeDefaultLocale();
+ if (runtimeDefaultLocale === localeCache.runtimeDefaultLocale)
+ return localeCache.defaultLocale;
+
+ // If we didn't have a cache hit, compute the candidate default locale.
+ // Then use it as the actual default locale if ICU supports that locale
+ // (perhaps via fallback). Otherwise use the last-ditch locale.
+ var candidate = DefaultLocaleIgnoringAvailableLocales();
+ var locale;
+ if (BestAvailableLocaleIgnoringDefault(callFunction(collatorInternalProperties.availableLocales,
+ collatorInternalProperties),
+ candidate) &&
+ BestAvailableLocaleIgnoringDefault(callFunction(numberFormatInternalProperties.availableLocales,
+ numberFormatInternalProperties),
+ candidate) &&
+ BestAvailableLocaleIgnoringDefault(callFunction(dateTimeFormatInternalProperties.availableLocales,
+ dateTimeFormatInternalProperties),
+ candidate))
+ {
+ locale = candidate;
+ } else {
+ locale = lastDitchLocale();
+ }
+
+ assert(IsStructurallyValidLanguageTag(locale),
+ "the computed default locale must be structurally valid");
+ assert(locale === CanonicalizeLanguageTag(locale),
+ "the computed default locale must be canonical");
+ assert(localeContainsNoUnicodeExtensions(locale),
+ "the computed default locale must not contain a Unicode extension sequence");
+
+ localeCache.defaultLocale = locale;
+ localeCache.runtimeDefaultLocale = runtimeDefaultLocale;
+
+ return locale;
+}
+
+
+/**
+ * Verifies that the given string is a well-formed ISO 4217 currency code.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 6.3.1.
+ */
+function getIsWellFormedCurrencyCodeRE() {
+ return internalIntlRegExps.isWellFormedCurrencyCodeRE ||
+ (internalIntlRegExps.isWellFormedCurrencyCodeRE = RegExpCreate("[^A-Z]"));
+}
+function IsWellFormedCurrencyCode(currency) {
+ var c = ToString(currency);
+ var normalized = toASCIIUpperCase(c);
+ if (normalized.length !== 3)
+ return false;
+ return !regexp_test_no_statics(getIsWellFormedCurrencyCodeRE(), normalized);
+}
+
+
+var timeZoneCache = {
+ icuDefaultTimeZone: undefined,
+ defaultTimeZone: undefined,
+};
+
+
+/**
+ * 6.4.2 CanonicalizeTimeZoneName ( timeZone )
+ *
+ * Canonicalizes the given IANA time zone name.
+ *
+ * ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3
+ */
+function CanonicalizeTimeZoneName(timeZone) {
+ assert(typeof timeZone === "string", "CanonicalizeTimeZoneName");
+
+ // Step 1. (Not applicable, the input is already a valid IANA time zone.)
+ assert(timeZone !== "Etc/Unknown", "Invalid time zone");
+ assert(timeZone === intl_IsValidTimeZoneName(timeZone), "Time zone name not normalized");
+
+ // Step 2.
+ var ianaTimeZone = intl_canonicalizeTimeZone(timeZone);
+ assert(ianaTimeZone !== "Etc/Unknown", "Invalid canonical time zone");
+ assert(ianaTimeZone === intl_IsValidTimeZoneName(ianaTimeZone), "Unsupported canonical time zone");
+
+ // Step 3.
+ if (ianaTimeZone === "Etc/UTC" || ianaTimeZone === "Etc/GMT") {
+ // ICU/CLDR canonicalizes Etc/UCT to Etc/GMT, but following IANA and
+ // ECMA-402 to the letter means Etc/UCT is a separate time zone.
+ if (timeZone === "Etc/UCT" || timeZone === "UCT")
+ ianaTimeZone = "Etc/UCT";
+ else
+ ianaTimeZone = "UTC";
+ }
+
+ // Step 4.
+ return ianaTimeZone;
+}
+
+
+/**
+ * 6.4.3 DefaultTimeZone ()
+ *
+ * Returns the IANA time zone name for the host environment's current time zone.
+ *
+ * ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3
+ */
+function DefaultTimeZone() {
+ const icuDefaultTimeZone = intl_defaultTimeZone();
+ if (timeZoneCache.icuDefaultTimeZone === icuDefaultTimeZone)
+ return timeZoneCache.defaultTimeZone;
+
+ // Verify that the current ICU time zone is a valid ECMA-402 time zone.
+ var timeZone = intl_IsValidTimeZoneName(icuDefaultTimeZone);
+ if (timeZone === null) {
+ // Before defaulting to "UTC", try to represent the default time zone
+ // using the Etc/GMT + offset format. This format only accepts full
+ // hour offsets.
+ const msPerHour = 60 * 60 * 1000;
+ var offset = intl_defaultTimeZoneOffset();
+ assert(offset === (offset | 0),
+ "milliseconds offset shouldn't be able to exceed int32_t range");
+ var offsetHours = offset / msPerHour, offsetHoursFraction = offset % msPerHour;
+ if (offsetHoursFraction === 0) {
+ // Etc/GMT + offset uses POSIX-style signs, i.e. a positive offset
+ // means a location west of GMT.
+ timeZone = "Etc/GMT" + (offsetHours < 0 ? "+" : "-") + std_Math_abs(offsetHours);
+
+ // Check if the fallback is valid.
+ timeZone = intl_IsValidTimeZoneName(timeZone);
+ }
+
+ // Fallback to "UTC" if everything else fails.
+ if (timeZone === null)
+ timeZone = "UTC";
+ }
+
+ // Canonicalize the ICU time zone, e.g. change Etc/UTC to UTC.
+ var defaultTimeZone = CanonicalizeTimeZoneName(timeZone);
+
+ timeZoneCache.defaultTimeZone = defaultTimeZone;
+ timeZoneCache.icuDefaultTimeZone = icuDefaultTimeZone;
+
+ return defaultTimeZone;
+}
+
+
+/********** Locale and Parameter Negotiation **********/
+
+/**
+ * Add old-style language tags without script code for locales that in current
+ * usage would include a script subtag. Also add an entry for the last-ditch
+ * locale, in case ICU doesn't directly support it (but does support it through
+ * fallback, e.g. supporting "en-GB" indirectly using "en" support).
+ */
+function addSpecialMissingLanguageTags(availableLocales) {
+ // Certain old-style language tags lack a script code, but in current usage
+ // they *would* include a script code. Map these over to modern forms.
+ var oldStyleLocales = std_Object_getOwnPropertyNames(oldStyleLanguageTagMappings);
+ for (var i = 0; i < oldStyleLocales.length; i++) {
+ var oldStyleLocale = oldStyleLocales[i];
+ if (availableLocales[oldStyleLanguageTagMappings[oldStyleLocale]])
+ availableLocales[oldStyleLocale] = true;
+ }
+
+ // Also forcibly provide the last-ditch locale.
+ var lastDitch = lastDitchLocale();
+ assert(lastDitch === "en-GB" && availableLocales["en"],
+ "shouldn't be a need to add every locale implied by the last-" +
+ "ditch locale, merely just the last-ditch locale");
+ availableLocales[lastDitch] = true;
+}
+
+
+/**
+ * Canonicalizes a locale list.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.2.1.
+ */
+function CanonicalizeLocaleList(locales) {
+ if (locales === undefined)
+ return new List();
+ var seen = new List();
+ if (typeof locales === "string")
+ locales = [locales];
+ var O = ToObject(locales);
+ var len = ToLength(O.length);
+ var k = 0;
+ while (k < len) {
+ // Don't call ToString(k) - SpiderMonkey is faster with integers.
+ var kPresent = HasProperty(O, k);
+ if (kPresent) {
+ var kValue = O[k];
+ if (!(typeof kValue === "string" || IsObject(kValue)))
+ ThrowTypeError(JSMSG_INVALID_LOCALES_ELEMENT);
+ var tag = ToString(kValue);
+ if (!IsStructurallyValidLanguageTag(tag))
+ ThrowRangeError(JSMSG_INVALID_LANGUAGE_TAG, tag);
+ tag = CanonicalizeLanguageTag(tag);
+ if (callFunction(ArrayIndexOf, seen, tag) === -1)
+ callFunction(std_Array_push, seen, tag);
+ }
+ k++;
+ }
+ return seen;
+}
+
+
+function BestAvailableLocaleHelper(availableLocales, locale, considerDefaultLocale) {
+ assert(IsStructurallyValidLanguageTag(locale), "invalid BestAvailableLocale locale structure");
+ assert(locale === CanonicalizeLanguageTag(locale), "non-canonical BestAvailableLocale locale");
+ assert(localeContainsNoUnicodeExtensions(locale), "locale must contain no Unicode extensions");
+
+ // In the spec, [[availableLocales]] is formally a list of all available
+ // locales. But in our implementation, it's an *incomplete* list, not
+ // necessarily including the default locale (and all locales implied by it,
+ // e.g. "de" implied by "de-CH"), if that locale isn't in every
+ // [[availableLocales]] list (because that locale is supported through
+ // fallback, e.g. "de-CH" supported through "de").
+ //
+ // If we're considering the default locale, augment the spec loop with
+ // additional checks to also test whether the current prefix is a prefix of
+ // the default locale.
+
+ var defaultLocale;
+ if (considerDefaultLocale)
+ defaultLocale = DefaultLocale();
+
+ var candidate = locale;
+ while (true) {
+ if (availableLocales[candidate])
+ return candidate;
+
+ if (considerDefaultLocale && candidate.length <= defaultLocale.length) {
+ if (candidate === defaultLocale)
+ return candidate;
+ if (callFunction(std_String_startsWith, defaultLocale, candidate + "-"))
+ return candidate;
+ }
+
+ var pos = callFunction(std_String_lastIndexOf, candidate, "-");
+ if (pos === -1)
+ return undefined;
+
+ if (pos >= 2 && candidate[pos - 2] === "-")
+ pos -= 2;
+
+ candidate = callFunction(String_substring, candidate, 0, pos);
+ }
+}
+
+
+/**
+ * Compares a BCP 47 language tag against the locales in availableLocales
+ * and returns the best available match. Uses the fallback
+ * mechanism of RFC 4647, section 3.4.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.2.2.
+ * Spec: RFC 4647, section 3.4.
+ */
+function BestAvailableLocale(availableLocales, locale) {
+ return BestAvailableLocaleHelper(availableLocales, locale, true);
+}
+
+
+/**
+ * Identical to BestAvailableLocale, but does not consider the default locale
+ * during computation.
+ */
+function BestAvailableLocaleIgnoringDefault(availableLocales, locale) {
+ return BestAvailableLocaleHelper(availableLocales, locale, false);
+}
+
+
+/**
+ * Compares a BCP 47 language priority list against the set of locales in
+ * availableLocales and determines the best available language to meet the
+ * request. Options specified through Unicode extension subsequences are
+ * ignored in the lookup, but information about such subsequences is returned
+ * separately.
+ *
+ * This variant is based on the Lookup algorithm of RFC 4647 section 3.4.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.2.3.
+ * Spec: RFC 4647, section 3.4.
+ */
+function LookupMatcher(availableLocales, requestedLocales) {
+ var i = 0;
+ var len = requestedLocales.length;
+ var availableLocale;
+ var locale, noExtensionsLocale;
+ while (i < len && availableLocale === undefined) {
+ locale = requestedLocales[i];
+ noExtensionsLocale = removeUnicodeExtensions(locale);
+ availableLocale = BestAvailableLocale(availableLocales, noExtensionsLocale);
+ i++;
+ }
+
+ var result = new Record();
+ if (availableLocale !== undefined) {
+ result.locale = availableLocale;
+ if (locale !== noExtensionsLocale) {
+ var unicodeLocaleExtensionSequenceRE = getUnicodeLocaleExtensionSequenceRE();
+ var extensionMatch = regexp_exec_no_statics(unicodeLocaleExtensionSequenceRE, locale);
+ var extension = extensionMatch[0];
+ var extensionIndex = extensionMatch.index;
+ result.extension = extension;
+ result.extensionIndex = extensionIndex;
+ }
+ } else {
+ result.locale = DefaultLocale();
+ }
+ return result;
+}
+
+
+/**
+ * Compares a BCP 47 language priority list against the set of locales in
+ * availableLocales and determines the best available language to meet the
+ * request. Options specified through Unicode extension subsequences are
+ * ignored in the lookup, but information about such subsequences is returned
+ * separately.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.2.4.
+ */
+function BestFitMatcher(availableLocales, requestedLocales) {
+ // this implementation doesn't have anything better
+ return LookupMatcher(availableLocales, requestedLocales);
+}
+
+
+/**
+ * Compares a BCP 47 language priority list against availableLocales and
+ * determines the best available language to meet the request. Options specified
+ * through Unicode extension subsequences are negotiated separately, taking the
+ * caller's relevant extensions and locale data as well as client-provided
+ * options into consideration.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.2.5.
+ */
+function ResolveLocale(availableLocales, requestedLocales, options, relevantExtensionKeys, localeData) {
+ /*jshint laxbreak: true */
+
+ // Steps 1-3.
+ var matcher = options.localeMatcher;
+ var r = (matcher === "lookup")
+ ? LookupMatcher(availableLocales, requestedLocales)
+ : BestFitMatcher(availableLocales, requestedLocales);
+
+ // Step 4.
+ var foundLocale = r.locale;
+
+ // Step 5.a.
+ var extension = r.extension;
+ var extensionIndex, extensionSubtags, extensionSubtagsLength;
+
+ // Step 5.
+ if (extension !== undefined) {
+ // Step 5.b.
+ extensionIndex = r.extensionIndex;
+
+ // Steps 5.d-e.
+ extensionSubtags = StringSplitString(ToString(extension), "-");
+ extensionSubtagsLength = extensionSubtags.length;
+ }
+
+ // Steps 6-7.
+ var result = new Record();
+ result.dataLocale = foundLocale;
+
+ // Step 8.
+ var supportedExtension = "-u";
+
+ // Steps 9-11.
+ var i = 0;
+ var len = relevantExtensionKeys.length;
+ while (i < len) {
+ // Steps 11.a-c.
+ var key = relevantExtensionKeys[i];
+
+ // In this implementation, localeData is a function, not an object.
+ var foundLocaleData = localeData(foundLocale);
+ var keyLocaleData = foundLocaleData[key];
+
+ // Locale data provides default value.
+ // Step 11.d.
+ var value = keyLocaleData[0];
+
+ // Locale tag may override.
+
+ // Step 11.e.
+ var supportedExtensionAddition = "";
+
+ // Step 11.f is implemented by Utilities.js.
+
+ var valuePos;
+
+ // Step 11.g.
+ if (extensionSubtags !== undefined) {
+ // Step 11.g.i.
+ var keyPos = callFunction(ArrayIndexOf, extensionSubtags, key);
+
+ // Step 11.g.ii.
+ if (keyPos !== -1) {
+ // Step 11.g.ii.1.
+ if (keyPos + 1 < extensionSubtagsLength &&
+ extensionSubtags[keyPos + 1].length > 2)
+ {
+ // Step 11.g.ii.1.a.
+ var requestedValue = extensionSubtags[keyPos + 1];
+
+ // Step 11.g.ii.1.b.
+ valuePos = callFunction(ArrayIndexOf, keyLocaleData, requestedValue);
+
+ // Step 11.g.ii.1.c.
+ if (valuePos !== -1) {
+ value = requestedValue;
+ supportedExtensionAddition = "-" + key + "-" + value;
+ }
+ } else {
+ // Step 11.g.ii.2.
+
+ // According to the LDML spec, if there's no type value,
+ // and true is an allowed value, it's used.
+
+ // Step 11.g.ii.2.a.
+ valuePos = callFunction(ArrayIndexOf, keyLocaleData, "true");
+
+ // Step 11.g.ii.2.b.
+ if (valuePos !== -1)
+ value = "true";
+ }
+ }
+ }
+
+ // Options override all.
+
+ // Step 11.h.i.
+ var optionsValue = options[key];
+
+ // Step 11.h, 11.h.ii.
+ if (optionsValue !== undefined &&
+ callFunction(ArrayIndexOf, keyLocaleData, optionsValue) !== -1)
+ {
+ // Step 11.h.ii.1.
+ if (optionsValue !== value) {
+ value = optionsValue;
+ supportedExtensionAddition = "";
+ }
+ }
+
+ // Steps 11.i-k.
+ result[key] = value;
+ supportedExtension += supportedExtensionAddition;
+ i++;
+ }
+
+ // Step 12.
+ if (supportedExtension.length > 2) {
+ var preExtension = callFunction(String_substring, foundLocale, 0, extensionIndex);
+ var postExtension = callFunction(String_substring, foundLocale, extensionIndex);
+ foundLocale = preExtension + supportedExtension + postExtension;
+ }
+
+ // Steps 13-14.
+ result.locale = foundLocale;
+ return result;
+}
+
+
+/**
+ * Returns the subset of requestedLocales for which availableLocales has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.2.6.
+ */
+function LookupSupportedLocales(availableLocales, requestedLocales) {
+ // Steps 1-2.
+ var len = requestedLocales.length;
+ var subset = new List();
+
+ // Steps 3-4.
+ var k = 0;
+ while (k < len) {
+ // Steps 4.a-b.
+ var locale = requestedLocales[k];
+ var noExtensionsLocale = removeUnicodeExtensions(locale);
+
+ // Step 4.c-d.
+ var availableLocale = BestAvailableLocale(availableLocales, noExtensionsLocale);
+ if (availableLocale !== undefined)
+ callFunction(std_Array_push, subset, locale);
+
+ // Step 4.e.
+ k++;
+ }
+
+ // Steps 5-6.
+ return callFunction(std_Array_slice, subset, 0);
+}
+
+
+/**
+ * Returns the subset of requestedLocales for which availableLocales has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.2.7.
+ */
+function BestFitSupportedLocales(availableLocales, requestedLocales) {
+ // don't have anything better
+ return LookupSupportedLocales(availableLocales, requestedLocales);
+}
+
+
+/**
+ * Returns the subset of requestedLocales for which availableLocales has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.2.8.
+ */
+function SupportedLocales(availableLocales, requestedLocales, options) {
+ /*jshint laxbreak: true */
+
+ // Step 1.
+ var matcher;
+ if (options !== undefined) {
+ // Steps 1.a-b.
+ options = ToObject(options);
+ matcher = options.localeMatcher;
+
+ // Step 1.c.
+ if (matcher !== undefined) {
+ matcher = ToString(matcher);
+ if (matcher !== "lookup" && matcher !== "best fit")
+ ThrowRangeError(JSMSG_INVALID_LOCALE_MATCHER, matcher);
+ }
+ }
+
+ // Steps 2-3.
+ var subset = (matcher === undefined || matcher === "best fit")
+ ? BestFitSupportedLocales(availableLocales, requestedLocales)
+ : LookupSupportedLocales(availableLocales, requestedLocales);
+
+ // Step 4.
+ for (var i = 0; i < subset.length; i++) {
+ _DefineDataProperty(subset, i, subset[i],
+ ATTR_ENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE);
+ }
+ _DefineDataProperty(subset, "length", subset.length,
+ ATTR_NONENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE);
+
+ // Step 5.
+ return subset;
+}
+
+
+/**
+ * Extracts a property value from the provided options object, converts it to
+ * the required type, checks whether it is one of a list of allowed values,
+ * and fills in a fallback value if necessary.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.2.9.
+ */
+function GetOption(options, property, type, values, fallback) {
+ // Step 1.
+ var value = options[property];
+
+ // Step 2.
+ if (value !== undefined) {
+ // Steps 2.a-c.
+ if (type === "boolean")
+ value = ToBoolean(value);
+ else if (type === "string")
+ value = ToString(value);
+ else
+ assert(false, "GetOption");
+
+ // Step 2.d.
+ if (values !== undefined && callFunction(ArrayIndexOf, values, value) === -1)
+ ThrowRangeError(JSMSG_INVALID_OPTION_VALUE, property, value);
+
+ // Step 2.e.
+ return value;
+ }
+
+ // Step 3.
+ return fallback;
+}
+
+/**
+ * Extracts a property value from the provided options object, converts it to a
+ * Number value, checks whether it is in the allowed range, and fills in a
+ * fallback value if necessary.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.2.10.
+ */
+function GetNumberOption(options, property, minimum, maximum, fallback) {
+ assert(typeof minimum === "number", "GetNumberOption");
+ assert(typeof maximum === "number", "GetNumberOption");
+ assert(fallback === undefined || (fallback >= minimum && fallback <= maximum), "GetNumberOption");
+
+ // Step 1.
+ var value = options[property];
+
+ // Step 2.
+ if (value !== undefined) {
+ value = ToNumber(value);
+ if (Number_isNaN(value) || value < minimum || value > maximum)
+ ThrowRangeError(JSMSG_INVALID_DIGITS_VALUE, value);
+ return std_Math_floor(value);
+ }
+
+ // Step 3.
+ return fallback;
+}
+
+
+/********** Property access for Intl objects **********/
+
+
+/**
+ * Weak map used to track the initialize-as-Intl status (and, if an object has
+ * been so initialized, the Intl-specific internal properties) of all objects.
+ * Presence of an object as a key within this map indicates that the object has
+ * its [[initializedIntlObject]] internal property set to true. The associated
+ * value is an object whose structure is documented in |initializeIntlObject|
+ * below.
+ *
+ * Ideally we'd be using private symbols for internal properties, but
+ * SpiderMonkey doesn't have those yet.
+ */
+var internalsMap = new WeakMap();
+
+
+/**
+ * Set the [[initializedIntlObject]] internal property of |obj| to true.
+ */
+function initializeIntlObject(obj) {
+ assert(IsObject(obj), "Non-object passed to initializeIntlObject");
+
+ // Intl-initialized objects are weird. They have [[initializedIntlObject]]
+ // set on them, but they don't *necessarily* have any other properties.
+
+ var internals = std_Object_create(null);
+
+ // The meaning of an internals object for an object |obj| is as follows.
+ //
+ // If the .type is "partial", |obj| has [[initializedIntlObject]] set but
+ // nothing else. No other property of |internals| can be used. (This
+ // occurs when InitializeCollator or similar marks an object as
+ // [[initializedIntlObject]] but fails before marking it as the appropriate
+ // more-specific type ["Collator", "DateTimeFormat", "NumberFormat"].)
+ //
+ // Otherwise, the .type indicates the type of Intl object that |obj| is:
+ // "Collator", "DateTimeFormat", or "NumberFormat" (likely with more coming
+ // in future Intl specs). In these cases |obj| *conceptually* also has
+ // [[initializedCollator]] or similar set, and all the other properties
+ // implied by that.
+ //
+ // If |internals| doesn't have a "partial" .type, two additional properties
+ // have meaning. The .lazyData property stores information needed to
+ // compute -- without observable side effects -- the actual internal Intl
+ // properties of |obj|. If it is non-null, then the actual internal
+ // properties haven't been computed, and .lazyData must be processed by
+ // |setInternalProperties| before internal Intl property values are
+ // available. If it is null, then the .internalProps property contains an
+ // object whose properties are the internal Intl properties of |obj|.
+
+ internals.type = "partial";
+ internals.lazyData = null;
+ internals.internalProps = null;
+
+ callFunction(std_WeakMap_set, internalsMap, obj, internals);
+ return internals;
+}
+
+
+/**
+ * Mark |internals| as having the given type and lazy data.
+ */
+function setLazyData(internals, type, lazyData)
+{
+ assert(internals.type === "partial", "can't set lazy data for anything but a newborn");
+ assert(type === "Collator" || type === "DateTimeFormat" || type == "NumberFormat", "bad type");
+ assert(IsObject(lazyData), "non-object lazy data");
+
+ // Set in reverse order so that the .type change is a barrier.
+ internals.lazyData = lazyData;
+ internals.type = type;
+}
+
+
+/**
+ * Set the internal properties object for an |internals| object previously
+ * associated with lazy data.
+ */
+function setInternalProperties(internals, internalProps)
+{
+ assert(internals.type !== "partial", "newborn internals can't have computed internals");
+ assert(IsObject(internals.lazyData), "lazy data must exist already");
+ assert(IsObject(internalProps), "internalProps argument should be an object");
+
+ // Set in reverse order so that the .lazyData nulling is a barrier.
+ internals.internalProps = internalProps;
+ internals.lazyData = null;
+}
+
+
+/**
+ * Get the existing internal properties out of a non-newborn |internals|, or
+ * null if none have been computed.
+ */
+function maybeInternalProperties(internals)
+{
+ assert(IsObject(internals), "non-object passed to maybeInternalProperties");
+ assert(internals.type !== "partial", "maybeInternalProperties must only be used on completely-initialized internals objects");
+ var lazyData = internals.lazyData;
+ if (lazyData)
+ return null;
+ assert(IsObject(internals.internalProps), "missing lazy data and computed internals");
+ return internals.internalProps;
+}
+
+
+/**
+ * Return whether |obj| has an[[initializedIntlObject]] property set to true.
+ */
+function isInitializedIntlObject(obj) {
+#ifdef DEBUG
+ var internals = callFunction(std_WeakMap_get, internalsMap, obj);
+ if (IsObject(internals)) {
+ assert(callFunction(std_Object_hasOwnProperty, internals, "type"), "missing type");
+ var type = internals.type;
+ assert(type === "partial" || type === "Collator" || type === "DateTimeFormat" || type === "NumberFormat", "unexpected type");
+ assert(callFunction(std_Object_hasOwnProperty, internals, "lazyData"), "missing lazyData");
+ assert(callFunction(std_Object_hasOwnProperty, internals, "internalProps"), "missing internalProps");
+ } else {
+ assert(internals === undefined, "bad mapping for |obj|");
+ }
+#endif
+ return callFunction(std_WeakMap_has, internalsMap, obj);
+}
+
+
+/**
+ * Check that |obj| meets the requirements for "this Collator object", "this
+ * NumberFormat object", or "this DateTimeFormat object" as used in the method
+ * with the given name. Throw a TypeError if |obj| doesn't meet these
+ * requirements. But if it does, return |obj|'s internals object (*not* the
+ * object holding its internal properties!), associated with it by
+ * |internalsMap|, with structure specified above.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 10.3.
+ * Spec: ECMAScript Internationalization API Specification, 11.3.
+ * Spec: ECMAScript Internationalization API Specification, 12.3.
+ */
+function getIntlObjectInternals(obj, className, methodName) {
+ assert(typeof className === "string", "bad className for getIntlObjectInternals");
+
+ var internals = callFunction(std_WeakMap_get, internalsMap, obj);
+ assert(internals === undefined || isInitializedIntlObject(obj), "bad mapping in internalsMap");
+
+ if (internals === undefined || internals.type !== className)
+ ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, className, methodName, className);
+
+ return internals;
+}
+
+
+/**
+ * Get the internal properties of known-Intl object |obj|. For use only by
+ * C++ code that knows what it's doing!
+ */
+function getInternals(obj)
+{
+ assert(isInitializedIntlObject(obj), "for use only on guaranteed Intl objects");
+
+ var internals = callFunction(std_WeakMap_get, internalsMap, obj);
+
+ assert(internals.type !== "partial", "must have been successfully initialized");
+ var lazyData = internals.lazyData;
+ if (!lazyData)
+ return internals.internalProps;
+
+ var internalProps;
+ var type = internals.type;
+ if (type === "Collator")
+ internalProps = resolveCollatorInternals(lazyData)
+ else if (type === "DateTimeFormat")
+ internalProps = resolveDateTimeFormatInternals(lazyData)
+ else
+ internalProps = resolveNumberFormatInternals(lazyData);
+ setInternalProperties(internals, internalProps);
+ return internalProps;
+}
+
+
+/********** Intl.Collator **********/
+
+
+/**
+ * Mapping from Unicode extension keys for collation to options properties,
+ * their types and permissible values.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 10.1.1.
+ */
+var collatorKeyMappings = {
+ kn: {property: "numeric", type: "boolean"},
+ kf: {property: "caseFirst", type: "string", values: ["upper", "lower", "false"]}
+};
+
+
+/**
+ * Compute an internal properties object from |lazyCollatorData|.
+ */
+function resolveCollatorInternals(lazyCollatorData)
+{
+ assert(IsObject(lazyCollatorData), "lazy data not an object?");
+
+ var internalProps = std_Object_create(null);
+
+ // Step 7.
+ internalProps.usage = lazyCollatorData.usage;
+
+ // Step 8.
+ var Collator = collatorInternalProperties;
+
+ // Step 9.
+ var collatorIsSorting = lazyCollatorData.usage === "sort";
+ var localeData = collatorIsSorting
+ ? Collator.sortLocaleData
+ : Collator.searchLocaleData;
+
+ // Compute effective locale.
+ // Step 14.
+ var relevantExtensionKeys = Collator.relevantExtensionKeys;
+
+ // Step 15.
+ var r = ResolveLocale(callFunction(Collator.availableLocales, Collator),
+ lazyCollatorData.requestedLocales,
+ lazyCollatorData.opt,
+ relevantExtensionKeys,
+ localeData);
+
+ // Step 16.
+ internalProps.locale = r.locale;
+
+ // Steps 17-19.
+ var key, property, value, mapping;
+ var i = 0, len = relevantExtensionKeys.length;
+ while (i < len) {
+ // Step 19.a.
+ key = relevantExtensionKeys[i];
+ if (key === "co") {
+ // Step 19.b.
+ property = "collation";
+ value = r.co === null ? "default" : r.co;
+ } else {
+ // Step 19.c.
+ mapping = collatorKeyMappings[key];
+ property = mapping.property;
+ value = r[key];
+ if (mapping.type === "boolean")
+ value = value === "true";
+ }
+
+ // Step 19.d.
+ internalProps[property] = value;
+
+ // Step 19.e.
+ i++;
+ }
+
+ // Compute remaining collation options.
+ // Steps 21-22.
+ var s = lazyCollatorData.rawSensitivity;
+ if (s === undefined) {
+ if (collatorIsSorting) {
+ // Step 21.a.
+ s = "variant";
+ } else {
+ // Step 21.b.
+ var dataLocale = r.dataLocale;
+ var dataLocaleData = localeData(dataLocale);
+ s = dataLocaleData.sensitivity;
+ }
+ }
+ internalProps.sensitivity = s;
+
+ // Step 24.
+ internalProps.ignorePunctuation = lazyCollatorData.ignorePunctuation;
+
+ // Step 25.
+ internalProps.boundFormat = undefined;
+
+ // The caller is responsible for associating |internalProps| with the right
+ // object using |setInternalProperties|.
+ return internalProps;
+}
+
+
+/**
+ * Returns an object containing the Collator internal properties of |obj|, or
+ * throws a TypeError if |obj| isn't Collator-initialized.
+ */
+function getCollatorInternals(obj, methodName) {
+ var internals = getIntlObjectInternals(obj, "Collator", methodName);
+ assert(internals.type === "Collator", "bad type escaped getIntlObjectInternals");
+
+ // If internal properties have already been computed, use them.
+ var internalProps = maybeInternalProperties(internals);
+ if (internalProps)
+ return internalProps;
+
+ // Otherwise it's time to fully create them.
+ internalProps = resolveCollatorInternals(internals.lazyData);
+ setInternalProperties(internals, internalProps);
+ return internalProps;
+}
+
+
+/**
+ * Initializes an object as a Collator.
+ *
+ * This method is complicated a moderate bit by its implementing initialization
+ * as a *lazy* concept. Everything that must happen now, does -- but we defer
+ * all the work we can until the object is actually used as a Collator. This
+ * later work occurs in |resolveCollatorInternals|; steps not noted here occur
+ * there.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 10.1.1.
+ */
+function InitializeCollator(collator, locales, options) {
+ assert(IsObject(collator), "InitializeCollator");
+
+ // Step 1.
+ if (isInitializedIntlObject(collator))
+ ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
+
+ // Step 2.
+ var internals = initializeIntlObject(collator);
+
+ // Lazy Collator data has the following structure:
+ //
+ // {
+ // requestedLocales: List of locales,
+ // usage: "sort" / "search",
+ // opt: // opt object computed in InitializeCollator
+ // {
+ // localeMatcher: "lookup" / "best fit",
+ // kn: true / false / undefined,
+ // kf: "upper" / "lower" / "false" / undefined
+ // }
+ // rawSensitivity: "base" / "accent" / "case" / "variant" / undefined,
+ // ignorePunctuation: true / false
+ // }
+ //
+ // Note that lazy data is only installed as a final step of initialization,
+ // so every Collator lazy data object has *all* these properties, never a
+ // subset of them.
+ var lazyCollatorData = std_Object_create(null);
+
+ // Step 3.
+ var requestedLocales = CanonicalizeLocaleList(locales);
+ lazyCollatorData.requestedLocales = requestedLocales;
+
+ // Steps 4-5.
+ //
+ // If we ever need more speed here at startup, we should try to detect the
+ // case where |options === undefined| and Object.prototype hasn't been
+ // mucked with. (|options| is fully consumed in this method, so it's not a
+ // concern that Object.prototype might be touched between now and when
+ // |resolveCollatorInternals| is called.) For now, just keep it simple.
+ if (options === undefined)
+ options = {};
+ else
+ options = ToObject(options);
+
+ // Compute options that impact interpretation of locale.
+ // Step 6.
+ var u = GetOption(options, "usage", "string", ["sort", "search"], "sort");
+ lazyCollatorData.usage = u;
+
+ // Step 10.
+ var opt = new Record();
+ lazyCollatorData.opt = opt;
+
+ // Steps 11-12.
+ var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
+ opt.localeMatcher = matcher;
+
+ // Step 13, unrolled.
+ var numericValue = GetOption(options, "numeric", "boolean", undefined, undefined);
+ if (numericValue !== undefined)
+ numericValue = numericValue ? 'true' : 'false';
+ opt.kn = numericValue;
+
+ var caseFirstValue = GetOption(options, "caseFirst", "string", ["upper", "lower", "false"], undefined);
+ opt.kf = caseFirstValue;
+
+ // Compute remaining collation options.
+ // Step 20.
+ var s = GetOption(options, "sensitivity", "string",
+ ["base", "accent", "case", "variant"], undefined);
+ lazyCollatorData.rawSensitivity = s;
+
+ // Step 23.
+ var ip = GetOption(options, "ignorePunctuation", "boolean", undefined, false);
+ lazyCollatorData.ignorePunctuation = ip;
+
+ // Step 26.
+ //
+ // We've done everything that must be done now: mark the lazy data as fully
+ // computed and install it.
+ setLazyData(internals, "Collator", lazyCollatorData);
+}
+
+
+/**
+ * Returns the subset of the given locale list for which this locale list has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 10.2.2.
+ */
+function Intl_Collator_supportedLocalesOf(locales /*, options*/) {
+ var options = arguments.length > 1 ? arguments[1] : undefined;
+
+ var availableLocales = callFunction(collatorInternalProperties.availableLocales,
+ collatorInternalProperties);
+ var requestedLocales = CanonicalizeLocaleList(locales);
+ return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
+
+/**
+ * Collator internal properties.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.1 and 10.2.3.
+ */
+var collatorInternalProperties = {
+ sortLocaleData: collatorSortLocaleData,
+ searchLocaleData: collatorSearchLocaleData,
+ _availableLocales: null,
+ availableLocales: function()
+ {
+ var locales = this._availableLocales;
+ if (locales)
+ return locales;
+
+ locales = intl_Collator_availableLocales();
+ addSpecialMissingLanguageTags(locales);
+ return (this._availableLocales = locales);
+ },
+ relevantExtensionKeys: ["co", "kn"]
+};
+
+
+function collatorSortLocaleData(locale) {
+ var collations = intl_availableCollations(locale);
+ callFunction(std_Array_unshift, collations, null);
+ return {
+ co: collations,
+ kn: ["false", "true"]
+ };
+}
+
+
+function collatorSearchLocaleData(locale) {
+ return {
+ co: [null],
+ kn: ["false", "true"],
+ // In theory the default sensitivity is locale dependent;
+ // in reality the CLDR/ICU default strength is always tertiary.
+ sensitivity: "variant"
+ };
+}
+
+
+/**
+ * Function to be bound and returned by Intl.Collator.prototype.format.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 12.3.2.
+ */
+function collatorCompareToBind(x, y) {
+ // Steps 1.a.i-ii implemented by ECMAScript declaration binding instantiation,
+ // ES5.1 10.5, step 4.d.ii.
+
+ // Step 1.a.iii-v.
+ var X = ToString(x);
+ var Y = ToString(y);
+ return intl_CompareStrings(this, X, Y);
+}
+
+
+/**
+ * Returns a function bound to this Collator that compares x (converted to a
+ * String value) and y (converted to a String value),
+ * and returns a number less than 0 if x < y, 0 if x = y, or a number greater
+ * than 0 if x > y according to the sort order for the locale and collation
+ * options of this Collator object.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 10.3.2.
+ */
+function Intl_Collator_compare_get() {
+ // Check "this Collator object" per introduction of section 10.3.
+ var internals = getCollatorInternals(this, "compare");
+
+ // Step 1.
+ if (internals.boundCompare === undefined) {
+ // Step 1.a.
+ var F = collatorCompareToBind;
+
+ // Step 1.b-d.
+ var bc = callFunction(FunctionBind, F, this);
+ internals.boundCompare = bc;
+ }
+
+ // Step 2.
+ return internals.boundCompare;
+}
+
+
+/**
+ * Returns the resolved options for a Collator object.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 10.3.3 and 10.4.
+ */
+function Intl_Collator_resolvedOptions() {
+ // Check "this Collator object" per introduction of section 10.3.
+ var internals = getCollatorInternals(this, "resolvedOptions");
+
+ var result = {
+ locale: internals.locale,
+ usage: internals.usage,
+ sensitivity: internals.sensitivity,
+ ignorePunctuation: internals.ignorePunctuation
+ };
+
+ var relevantExtensionKeys = collatorInternalProperties.relevantExtensionKeys;
+ for (var i = 0; i < relevantExtensionKeys.length; i++) {
+ var key = relevantExtensionKeys[i];
+ var property = (key === "co") ? "collation" : collatorKeyMappings[key].property;
+ _DefineDataProperty(result, property, internals[property]);
+ }
+ return result;
+}
+
+
+/********** Intl.NumberFormat **********/
+
+
+/**
+ * NumberFormat internal properties.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.1 and 11.2.3.
+ */
+var numberFormatInternalProperties = {
+ localeData: numberFormatLocaleData,
+ _availableLocales: null,
+ availableLocales: function()
+ {
+ var locales = this._availableLocales;
+ if (locales)
+ return locales;
+
+ locales = intl_NumberFormat_availableLocales();
+ addSpecialMissingLanguageTags(locales);
+ return (this._availableLocales = locales);
+ },
+ relevantExtensionKeys: ["nu"]
+};
+
+
+/**
+ * Compute an internal properties object from |lazyNumberFormatData|.
+ */
+function resolveNumberFormatInternals(lazyNumberFormatData) {
+ assert(IsObject(lazyNumberFormatData), "lazy data not an object?");
+
+ var internalProps = std_Object_create(null);
+
+ // Step 3.
+ var requestedLocales = lazyNumberFormatData.requestedLocales;
+
+ // Compute options that impact interpretation of locale.
+ // Step 6.
+ var opt = lazyNumberFormatData.opt;
+
+ // Compute effective locale.
+ // Step 9.
+ var NumberFormat = numberFormatInternalProperties;
+
+ // Step 10.
+ var localeData = NumberFormat.localeData;
+
+ // Step 11.
+ var r = ResolveLocale(callFunction(NumberFormat.availableLocales, NumberFormat),
+ lazyNumberFormatData.requestedLocales,
+ lazyNumberFormatData.opt,
+ NumberFormat.relevantExtensionKeys,
+ localeData);
+
+ // Steps 12-13. (Step 14 is not relevant to our implementation.)
+ internalProps.locale = r.locale;
+ internalProps.numberingSystem = r.nu;
+
+ // Compute formatting options.
+ // Step 16.
+ var s = lazyNumberFormatData.style;
+ internalProps.style = s;
+
+ // Steps 20, 22.
+ if (s === "currency") {
+ internalProps.currency = lazyNumberFormatData.currency;
+ internalProps.currencyDisplay = lazyNumberFormatData.currencyDisplay;
+ }
+
+ // Step 24.
+ internalProps.minimumIntegerDigits = lazyNumberFormatData.minimumIntegerDigits;
+
+ // Steps 27.
+ internalProps.minimumFractionDigits = lazyNumberFormatData.minimumFractionDigits;
+
+ // Step 30.
+ internalProps.maximumFractionDigits = lazyNumberFormatData.maximumFractionDigits;
+
+ // Step 33.
+ if ("minimumSignificantDigits" in lazyNumberFormatData) {
+ // Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the
+ // actual presence (versus undefined-ness) of these properties.
+ assert("maximumSignificantDigits" in lazyNumberFormatData, "min/max sig digits mismatch");
+ internalProps.minimumSignificantDigits = lazyNumberFormatData.minimumSignificantDigits;
+ internalProps.maximumSignificantDigits = lazyNumberFormatData.maximumSignificantDigits;
+ }
+
+ // Step 35.
+ internalProps.useGrouping = lazyNumberFormatData.useGrouping;
+
+ // Step 42.
+ internalProps.boundFormat = undefined;
+
+ // The caller is responsible for associating |internalProps| with the right
+ // object using |setInternalProperties|.
+ return internalProps;
+}
+
+
+/**
+ * Returns an object containing the NumberFormat internal properties of |obj|,
+ * or throws a TypeError if |obj| isn't NumberFormat-initialized.
+ */
+function getNumberFormatInternals(obj, methodName) {
+ var internals = getIntlObjectInternals(obj, "NumberFormat", methodName);
+ assert(internals.type === "NumberFormat", "bad type escaped getIntlObjectInternals");
+
+ // If internal properties have already been computed, use them.
+ var internalProps = maybeInternalProperties(internals);
+ if (internalProps)
+ return internalProps;
+
+ // Otherwise it's time to fully create them.
+ internalProps = resolveNumberFormatInternals(internals.lazyData);
+ setInternalProperties(internals, internalProps);
+ return internalProps;
+}
+
+
+/**
+ * Initializes an object as a NumberFormat.
+ *
+ * This method is complicated a moderate bit by its implementing initialization
+ * as a *lazy* concept. Everything that must happen now, does -- but we defer
+ * all the work we can until the object is actually used as a NumberFormat.
+ * This later work occurs in |resolveNumberFormatInternals|; steps not noted
+ * here occur there.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 11.1.1.
+ */
+function InitializeNumberFormat(numberFormat, locales, options) {
+ assert(IsObject(numberFormat), "InitializeNumberFormat");
+
+ // Step 1.
+ if (isInitializedIntlObject(numberFormat))
+ ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
+
+ // Step 2.
+ var internals = initializeIntlObject(numberFormat);
+
+ // Lazy NumberFormat data has the following structure:
+ //
+ // {
+ // requestedLocales: List of locales,
+ // style: "decimal" / "percent" / "currency",
+ //
+ // // fields present only if style === "currency":
+ // currency: a well-formed currency code (IsWellFormedCurrencyCode),
+ // currencyDisplay: "code" / "symbol" / "name",
+ //
+ // opt: // opt object computed in InitializeNumberFormat
+ // {
+ // localeMatcher: "lookup" / "best fit",
+ // }
+ //
+ // minimumIntegerDigits: integer ∈ [1, 21],
+ // minimumFractionDigits: integer ∈ [0, 20],
+ // maximumFractionDigits: integer ∈ [0, 20],
+ //
+ // // optional
+ // minimumSignificantDigits: integer ∈ [1, 21],
+ // maximumSignificantDigits: integer ∈ [1, 21],
+ //
+ // useGrouping: true / false,
+ // }
+ //
+ // Note that lazy data is only installed as a final step of initialization,
+ // so every Collator lazy data object has *all* these properties, never a
+ // subset of them.
+ var lazyNumberFormatData = std_Object_create(null);
+
+ // Step 3.
+ var requestedLocales = CanonicalizeLocaleList(locales);
+ lazyNumberFormatData.requestedLocales = requestedLocales;
+
+ // Steps 4-5.
+ //
+ // If we ever need more speed here at startup, we should try to detect the
+ // case where |options === undefined| and Object.prototype hasn't been
+ // mucked with. (|options| is fully consumed in this method, so it's not a
+ // concern that Object.prototype might be touched between now and when
+ // |resolveNumberFormatInternals| is called.) For now just keep it simple.
+ if (options === undefined)
+ options = {};
+ else
+ options = ToObject(options);
+
+ // Compute options that impact interpretation of locale.
+ // Step 6.
+ var opt = new Record();
+ lazyNumberFormatData.opt = opt;
+
+ // Steps 7-8.
+ var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
+ opt.localeMatcher = matcher;
+
+ // Compute formatting options.
+ // Step 15.
+ var s = GetOption(options, "style", "string", ["decimal", "percent", "currency"], "decimal");
+ lazyNumberFormatData.style = s;
+
+ // Steps 17-20.
+ var c = GetOption(options, "currency", "string", undefined, undefined);
+ if (c !== undefined && !IsWellFormedCurrencyCode(c))
+ ThrowRangeError(JSMSG_INVALID_CURRENCY_CODE, c);
+ var cDigits;
+ if (s === "currency") {
+ if (c === undefined)
+ ThrowTypeError(JSMSG_UNDEFINED_CURRENCY);
+
+ // Steps 20.a-c.
+ c = toASCIIUpperCase(c);
+ lazyNumberFormatData.currency = c;
+ cDigits = CurrencyDigits(c);
+ }
+
+ // Step 21.
+ var cd = GetOption(options, "currencyDisplay", "string", ["code", "symbol", "name"], "symbol");
+ if (s === "currency")
+ lazyNumberFormatData.currencyDisplay = cd;
+
+ // Step 23.
+ var mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1);
+ lazyNumberFormatData.minimumIntegerDigits = mnid;
+
+ // Steps 25-26.
+ var mnfdDefault = (s === "currency") ? cDigits : 0;
+ var mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault);
+ lazyNumberFormatData.minimumFractionDigits = mnfd;
+
+ // Steps 28-29.
+ var mxfdDefault;
+ if (s === "currency")
+ mxfdDefault = std_Math_max(mnfd, cDigits);
+ else if (s === "percent")
+ mxfdDefault = std_Math_max(mnfd, 0);
+ else
+ mxfdDefault = std_Math_max(mnfd, 3);
+ var mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20, mxfdDefault);
+ lazyNumberFormatData.maximumFractionDigits = mxfd;
+
+ // Steps 31-32.
+ var mnsd = options.minimumSignificantDigits;
+ var mxsd = options.maximumSignificantDigits;
+
+ // Step 33.
+ if (mnsd !== undefined || mxsd !== undefined) {
+ mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1);
+ mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21);
+ lazyNumberFormatData.minimumSignificantDigits = mnsd;
+ lazyNumberFormatData.maximumSignificantDigits = mxsd;
+ }
+
+ // Step 34.
+ var g = GetOption(options, "useGrouping", "boolean", undefined, true);
+ lazyNumberFormatData.useGrouping = g;
+
+ // Step 43.
+ //
+ // We've done everything that must be done now: mark the lazy data as fully
+ // computed and install it.
+ setLazyData(internals, "NumberFormat", lazyNumberFormatData);
+}
+
+
+/**
+ * Mapping from currency codes to the number of decimal digits used for them.
+ * Default is 2 digits.
+ *
+ * Spec: ISO 4217 Currency and Funds Code List.
+ * http://www.currency-iso.org/en/home/tables/table-a1.html
+ */
+var currencyDigits = {
+ BHD: 3,
+ BIF: 0,
+ BYR: 0,
+ CLF: 4,
+ CLP: 0,
+ DJF: 0,
+ GNF: 0,
+ IQD: 3,
+ ISK: 0,
+ JOD: 3,
+ JPY: 0,
+ KMF: 0,
+ KRW: 0,
+ KWD: 3,
+ LYD: 3,
+ OMR: 3,
+ PYG: 0,
+ RWF: 0,
+ TND: 3,
+ UGX: 0,
+ UYI: 0,
+ VND: 0,
+ VUV: 0,
+ XAF: 0,
+ XOF: 0,
+ XPF: 0
+};
+
+
+/**
+ * Returns the number of decimal digits to be used for the given currency.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 11.1.1.
+ */
+function getCurrencyDigitsRE() {
+ return internalIntlRegExps.currencyDigitsRE ||
+ (internalIntlRegExps.currencyDigitsRE = RegExpCreate("^[A-Z]{3}$"));
+}
+function CurrencyDigits(currency) {
+ assert(typeof currency === "string", "CurrencyDigits");
+ assert(regexp_test_no_statics(getCurrencyDigitsRE(), currency), "CurrencyDigits");
+
+ if (callFunction(std_Object_hasOwnProperty, currencyDigits, currency))
+ return currencyDigits[currency];
+ return 2;
+}
+
+
+/**
+ * Returns the subset of the given locale list for which this locale list has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 11.2.2.
+ */
+function Intl_NumberFormat_supportedLocalesOf(locales /*, options*/) {
+ var options = arguments.length > 1 ? arguments[1] : undefined;
+
+ var availableLocales = callFunction(numberFormatInternalProperties.availableLocales,
+ numberFormatInternalProperties);
+ var requestedLocales = CanonicalizeLocaleList(locales);
+ return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
+
+function getNumberingSystems(locale) {
+ // ICU doesn't have an API to determine the set of numbering systems
+ // supported for a locale; it generally pretends that any numbering system
+ // can be used with any locale. Supporting a decimal numbering system
+ // (where only the digits are replaced) is easy, so we offer them all here.
+ // Algorithmic numbering systems are typically tied to one locale, so for
+ // lack of information we don't offer them. To increase chances that
+ // other software will process output correctly, we further restrict to
+ // those decimal numbering systems explicitly listed in table 2 of
+ // the ECMAScript Internationalization API Specification, 11.3.2, which
+ // in turn are those with full specifications in version 21 of Unicode
+ // Technical Standard #35 using digits that were defined in Unicode 5.0,
+ // the Unicode version supported in Windows Vista.
+ // The one thing we can find out from ICU is the default numbering system
+ // for a locale.
+ var defaultNumberingSystem = intl_numberingSystem(locale);
+ return [
+ defaultNumberingSystem,
+ "arab", "arabext", "bali", "beng", "deva",
+ "fullwide", "gujr", "guru", "hanidec", "khmr",
+ "knda", "laoo", "latn", "limb", "mlym",
+ "mong", "mymr", "orya", "tamldec", "telu",
+ "thai", "tibt"
+ ];
+}
+
+
+function numberFormatLocaleData(locale) {
+ return {
+ nu: getNumberingSystems(locale)
+ };
+}
+
+
+/**
+ * Function to be bound and returned by Intl.NumberFormat.prototype.format.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 11.3.2.
+ */
+function numberFormatFormatToBind(value) {
+ // Steps 1.a.i implemented by ECMAScript declaration binding instantiation,
+ // ES5.1 10.5, step 4.d.ii.
+
+ // Step 1.a.ii-iii.
+ var x = ToNumber(value);
+ return intl_FormatNumber(this, x);
+}
+
+
+/**
+ * Returns a function bound to this NumberFormat that returns a String value
+ * representing the result of calling ToNumber(value) according to the
+ * effective locale and the formatting options of this NumberFormat.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 11.3.2.
+ */
+function Intl_NumberFormat_format_get() {
+ // Check "this NumberFormat object" per introduction of section 11.3.
+ var internals = getNumberFormatInternals(this, "format");
+
+ // Step 1.
+ if (internals.boundFormat === undefined) {
+ // Step 1.a.
+ var F = numberFormatFormatToBind;
+
+ // Step 1.b-d.
+ var bf = callFunction(FunctionBind, F, this);
+ internals.boundFormat = bf;
+ }
+ // Step 2.
+ return internals.boundFormat;
+}
+
+
+/**
+ * Returns the resolved options for a NumberFormat object.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 11.3.3 and 11.4.
+ */
+function Intl_NumberFormat_resolvedOptions() {
+ // Check "this NumberFormat object" per introduction of section 11.3.
+ var internals = getNumberFormatInternals(this, "resolvedOptions");
+
+ var result = {
+ locale: internals.locale,
+ numberingSystem: internals.numberingSystem,
+ style: internals.style,
+ minimumIntegerDigits: internals.minimumIntegerDigits,
+ minimumFractionDigits: internals.minimumFractionDigits,
+ maximumFractionDigits: internals.maximumFractionDigits,
+ useGrouping: internals.useGrouping
+ };
+ var optionalProperties = [
+ "currency",
+ "currencyDisplay",
+ "minimumSignificantDigits",
+ "maximumSignificantDigits"
+ ];
+ for (var i = 0; i < optionalProperties.length; i++) {
+ var p = optionalProperties[i];
+ if (callFunction(std_Object_hasOwnProperty, internals, p))
+ _DefineDataProperty(result, p, internals[p]);
+ }
+ return result;
+}
+
+
+/********** Intl.DateTimeFormat **********/
+
+
+/**
+ * Compute an internal properties object from |lazyDateTimeFormatData|.
+ */
+function resolveDateTimeFormatInternals(lazyDateTimeFormatData) {
+ assert(IsObject(lazyDateTimeFormatData), "lazy data not an object?");
+
+ // Lazy DateTimeFormat data has the following structure:
+ //
+ // {
+ // requestedLocales: List of locales,
+ //
+ // localeOpt: // *first* opt computed in InitializeDateTimeFormat
+ // {
+ // localeMatcher: "lookup" / "best fit",
+ //
+ // hour12: true / false, // optional
+ // }
+ //
+ // timeZone: IANA time zone name,
+ //
+ // formatOpt: // *second* opt computed in InitializeDateTimeFormat
+ // {
+ // // all the properties/values listed in Table 3
+ // // (weekday, era, year, month, day, &c.)
+ // }
+ //
+ // formatMatcher: "basic" / "best fit",
+ // }
+ //
+ // Note that lazy data is only installed as a final step of initialization,
+ // so every DateTimeFormat lazy data object has *all* these properties,
+ // never a subset of them.
+
+ var internalProps = std_Object_create(null);
+
+ // Compute effective locale.
+ // Step 8.
+ var DateTimeFormat = dateTimeFormatInternalProperties;
+
+ // Step 9.
+ var localeData = DateTimeFormat.localeData;
+
+ // Step 10.
+ var r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat),
+ lazyDateTimeFormatData.requestedLocales,
+ lazyDateTimeFormatData.localeOpt,
+ DateTimeFormat.relevantExtensionKeys,
+ localeData);
+
+ // Steps 11-13.
+ internalProps.locale = r.locale;
+ internalProps.calendar = r.ca;
+ internalProps.numberingSystem = r.nu;
+
+ // Compute formatting options.
+ // Step 14.
+ var dataLocale = r.dataLocale;
+
+ // Steps 15-17.
+ var tz = lazyDateTimeFormatData.timeZone;
+ if (tz === undefined) {
+ // Step 16.
+ tz = DefaultTimeZone();
+ }
+ internalProps.timeZone = tz;
+
+ // Step 18.
+ var formatOpt = lazyDateTimeFormatData.formatOpt;
+
+ // Steps 27-28, more or less - see comment after this function.
+ var pattern = toBestICUPattern(dataLocale, formatOpt);
+
+ // Step 29.
+ internalProps.pattern = pattern;
+
+ // Step 30.
+ internalProps.boundFormat = undefined;
+
+ // The caller is responsible for associating |internalProps| with the right
+ // object using |setInternalProperties|.
+ return internalProps;
+}
+
+
+/**
+ * Returns an object containing the DateTimeFormat internal properties of |obj|,
+ * or throws a TypeError if |obj| isn't DateTimeFormat-initialized.
+ */
+function getDateTimeFormatInternals(obj, methodName) {
+ var internals = getIntlObjectInternals(obj, "DateTimeFormat", methodName);
+ assert(internals.type === "DateTimeFormat", "bad type escaped getIntlObjectInternals");
+
+ // If internal properties have already been computed, use them.
+ var internalProps = maybeInternalProperties(internals);
+ if (internalProps)
+ return internalProps;
+
+ // Otherwise it's time to fully create them.
+ internalProps = resolveDateTimeFormatInternals(internals.lazyData);
+ setInternalProperties(internals, internalProps);
+ return internalProps;
+}
+
+/**
+ * Components of date and time formats and their values.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 12.1.1.
+ */
+var dateTimeComponentValues = {
+ weekday: ["narrow", "short", "long"],
+ era: ["narrow", "short", "long"],
+ year: ["2-digit", "numeric"],
+ month: ["2-digit", "numeric", "narrow", "short", "long"],
+ day: ["2-digit", "numeric"],
+ hour: ["2-digit", "numeric"],
+ minute: ["2-digit", "numeric"],
+ second: ["2-digit", "numeric"],
+ timeZoneName: ["short", "long"]
+};
+
+
+var dateTimeComponents = std_Object_getOwnPropertyNames(dateTimeComponentValues);
+
+
+/**
+ * Initializes an object as a DateTimeFormat.
+ *
+ * This method is complicated a moderate bit by its implementing initialization
+ * as a *lazy* concept. Everything that must happen now, does -- but we defer
+ * all the work we can until the object is actually used as a DateTimeFormat.
+ * This later work occurs in |resolveDateTimeFormatInternals|; steps not noted
+ * here occur there.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 12.1.1.
+ */
+function InitializeDateTimeFormat(dateTimeFormat, locales, options) {
+ assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat");
+
+ // Step 1.
+ if (isInitializedIntlObject(dateTimeFormat))
+ ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
+
+ // Step 2.
+ var internals = initializeIntlObject(dateTimeFormat);
+
+ // Lazy DateTimeFormat data has the following structure:
+ //
+ // {
+ // requestedLocales: List of locales,
+ //
+ // localeOpt: // *first* opt computed in InitializeDateTimeFormat
+ // {
+ // localeMatcher: "lookup" / "best fit",
+ // }
+ //
+ // timeZone: IANA time zone name,
+ //
+ // formatOpt: // *second* opt computed in InitializeDateTimeFormat
+ // {
+ // // all the properties/values listed in Table 3
+ // // (weekday, era, year, month, day, &c.)
+ //
+ // hour12: true / false // optional
+ // }
+ //
+ // formatMatcher: "basic" / "best fit",
+ // }
+ //
+ // Note that lazy data is only installed as a final step of initialization,
+ // so every DateTimeFormat lazy data object has *all* these properties,
+ // never a subset of them.
+ var lazyDateTimeFormatData = std_Object_create(null);
+
+ // Step 3.
+ var requestedLocales = CanonicalizeLocaleList(locales);
+ lazyDateTimeFormatData.requestedLocales = requestedLocales;
+
+ // Step 4.
+ options = ToDateTimeOptions(options, "any", "date");
+
+ // Compute options that impact interpretation of locale.
+ // Step 5.
+ var localeOpt = new Record();
+ lazyDateTimeFormatData.localeOpt = localeOpt;
+
+ // Steps 6-7.
+ var localeMatcher =
+ GetOption(options, "localeMatcher", "string", ["lookup", "best fit"],
+ "best fit");
+ localeOpt.localeMatcher = localeMatcher;
+
+ // Steps 15-17.
+ var tz = options.timeZone;
+ if (tz !== undefined) {
+ // Step 15.a.
+ tz = ToString(tz);
+
+ // Step 15.b.
+ var timeZone = intl_IsValidTimeZoneName(tz);
+ if (timeZone === null)
+ ThrowRangeError(JSMSG_INVALID_TIME_ZONE, tz);
+
+ // Step 15.c.
+ tz = CanonicalizeTimeZoneName(timeZone);
+ }
+ lazyDateTimeFormatData.timeZone = tz;
+
+ // Step 18.
+ var formatOpt = new Record();
+ lazyDateTimeFormatData.formatOpt = formatOpt;
+
+ // Step 19.
+ var i, prop;
+ for (i = 0; i < dateTimeComponents.length; i++) {
+ prop = dateTimeComponents[i];
+ var value = GetOption(options, prop, "string", dateTimeComponentValues[prop], undefined);
+ formatOpt[prop] = value;
+ }
+
+ // Steps 20-21 provided by ICU - see comment after this function.
+
+ // Step 22.
+ //
+ // For some reason (ICU not exposing enough interface?) we drop the
+ // requested format matcher on the floor after this. In any case, even if
+ // doing so is justified, we have to do this work here in case it triggers
+ // getters or similar. (bug 852837)
+ var formatMatcher =
+ GetOption(options, "formatMatcher", "string", ["basic", "best fit"],
+ "best fit");
+
+ // Steps 23-25 provided by ICU, more or less - see comment after this function.
+
+ // Step 26.
+ var hr12 = GetOption(options, "hour12", "boolean", undefined, undefined);
+
+ // Pass hr12 on to ICU.
+ if (hr12 !== undefined)
+ formatOpt.hour12 = hr12;
+
+ // Step 31.
+ //
+ // We've done everything that must be done now: mark the lazy data as fully
+ // computed and install it.
+ setLazyData(internals, "DateTimeFormat", lazyDateTimeFormatData);
+}
+
+
+// Intl.DateTimeFormat and ICU skeletons and patterns
+// ==================================================
+//
+// Different locales have different ways to display dates using the same
+// basic components. For example, en-US might use "Sept. 24, 2012" while
+// fr-FR might use "24 Sept. 2012". The intent of Intl.DateTimeFormat is to
+// permit production of a format for the locale that best matches the
+// set of date-time components and their desired representation as specified
+// by the API client.
+//
+// ICU supports specification of date and time formats in three ways:
+//
+// 1) A style is just one of the identifiers FULL, LONG, MEDIUM, or SHORT.
+// The date-time components included in each style and their representation
+// are defined by ICU using CLDR locale data (CLDR is the Unicode
+// Consortium's Common Locale Data Repository).
+//
+// 2) A skeleton is a string specifying which date-time components to include,
+// and which representations to use for them. For example, "yyyyMMMMdd"
+// specifies a year with at least four digits, a full month name, and a
+// two-digit day. It does not specify in which order the components appear,
+// how they are separated, the localized strings for textual components
+// (such as weekday or month), whether the month is in format or
+// stand-alone form¹, or the numbering system used for numeric components.
+// All that information is filled in by ICU using CLDR locale data.
+// ¹ The format form is the one used in formatted strings that include a
+// day; the stand-alone form is used when not including days, e.g., in
+// calendar headers. The two forms differ at least in some Slavic languages,
+// e.g. Russian: "22 марта 2013 г." vs. "Март 2013".
+//
+// 3) A pattern is a string specifying which date-time components to include,
+// in which order, with which separators, in which grammatical case. For
+// example, "EEEE, d MMMM y" specifies the full localized weekday name,
+// followed by comma and space, followed by the day, followed by space,
+// followed by the full month name in format form, followed by space,
+// followed by the full year. It
+// still does not specify localized strings for textual components and the
+// numbering system - these are determined by ICU using CLDR locale data or
+// possibly API parameters.
+//
+// All actual formatting in ICU is done with patterns; styles and skeletons
+// have to be mapped to patterns before processing.
+//
+// The options of DateTimeFormat most closely correspond to ICU skeletons. This
+// implementation therefore, in the toBestICUPattern function, converts
+// DateTimeFormat options to ICU skeletons, and then lets ICU map skeletons to
+// actual ICU patterns. The pattern may not directly correspond to what the
+// skeleton requests, as the mapper (UDateTimePatternGenerator) is constrained
+// by the available locale data for the locale. The resulting ICU pattern is
+// kept as the DateTimeFormat's [[pattern]] internal property and passed to ICU
+// in the format method.
+//
+// An ICU pattern represents the information of the following DateTimeFormat
+// internal properties described in the specification, which therefore don't
+// exist separately in the implementation:
+// - [[weekday]], [[era]], [[year]], [[month]], [[day]], [[hour]], [[minute]],
+// [[second]], [[timeZoneName]]
+// - [[hour12]]
+// - [[hourNo0]]
+// When needed for the resolvedOptions method, the resolveICUPattern function
+// maps the instance's ICU pattern back to the specified properties of the
+// object returned by resolvedOptions.
+//
+// ICU date-time skeletons and patterns aren't fully documented in the ICU
+// documentation (see http://bugs.icu-project.org/trac/ticket/9627). The best
+// documentation at this point is in UTR 35:
+// http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
+
+
+/**
+ * Returns an ICU pattern string for the given locale and representing the
+ * specified options as closely as possible given available locale data.
+ */
+function toBestICUPattern(locale, options) {
+ // Create an ICU skeleton representing the specified options. See
+ // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
+ var skeleton = "";
+ switch (options.weekday) {
+ case "narrow":
+ skeleton += "EEEEE";
+ break;
+ case "short":
+ skeleton += "E";
+ break;
+ case "long":
+ skeleton += "EEEE";
+ }
+ switch (options.era) {
+ case "narrow":
+ skeleton += "GGGGG";
+ break;
+ case "short":
+ skeleton += "G";
+ break;
+ case "long":
+ skeleton += "GGGG";
+ break;
+ }
+ switch (options.year) {
+ case "2-digit":
+ skeleton += "yy";
+ break;
+ case "numeric":
+ skeleton += "y";
+ break;
+ }
+ switch (options.month) {
+ case "2-digit":
+ skeleton += "MM";
+ break;
+ case "numeric":
+ skeleton += "M";
+ break;
+ case "narrow":
+ skeleton += "MMMMM";
+ break;
+ case "short":
+ skeleton += "MMM";
+ break;
+ case "long":
+ skeleton += "MMMM";
+ break;
+ }
+ switch (options.day) {
+ case "2-digit":
+ skeleton += "dd";
+ break;
+ case "numeric":
+ skeleton += "d";
+ break;
+ }
+ var hourSkeletonChar = "j";
+ if (options.hour12 !== undefined) {
+ if (options.hour12)
+ hourSkeletonChar = "h";
+ else
+ hourSkeletonChar = "H";
+ }
+ switch (options.hour) {
+ case "2-digit":
+ skeleton += hourSkeletonChar + hourSkeletonChar;
+ break;
+ case "numeric":
+ skeleton += hourSkeletonChar;
+ break;
+ }
+ switch (options.minute) {
+ case "2-digit":
+ skeleton += "mm";
+ break;
+ case "numeric":
+ skeleton += "m";
+ break;
+ }
+ switch (options.second) {
+ case "2-digit":
+ skeleton += "ss";
+ break;
+ case "numeric":
+ skeleton += "s";
+ break;
+ }
+ switch (options.timeZoneName) {
+ case "short":
+ skeleton += "z";
+ break;
+ case "long":
+ skeleton += "zzzz";
+ break;
+ }
+
+ // Let ICU convert the ICU skeleton to an ICU pattern for the given locale.
+ return intl_patternForSkeleton(locale, skeleton);
+}
+
+
+/**
+ * Returns a new options object that includes the provided options (if any)
+ * and fills in default components if required components are not defined.
+ * Required can be "date", "time", or "any".
+ * Defaults can be "date", "time", or "all".
+ *
+ * Spec: ECMAScript Internationalization API Specification, 12.1.1.
+ */
+function ToDateTimeOptions(options, required, defaults) {
+ assert(typeof required === "string", "ToDateTimeOptions");
+ assert(typeof defaults === "string", "ToDateTimeOptions");
+
+ // Steps 1-3.
+ if (options === undefined)
+ options = null;
+ else
+ options = ToObject(options);
+ options = std_Object_create(options);
+
+ // Step 4.
+ var needDefaults = true;
+
+ // Step 5.
+ if ((required === "date" || required === "any") &&
+ (options.weekday !== undefined || options.year !== undefined ||
+ options.month !== undefined || options.day !== undefined))
+ {
+ needDefaults = false;
+ }
+
+ // Step 6.
+ if ((required === "time" || required === "any") &&
+ (options.hour !== undefined || options.minute !== undefined ||
+ options.second !== undefined))
+ {
+ needDefaults = false;
+ }
+
+ // Step 7.
+ if (needDefaults && (defaults === "date" || defaults === "all")) {
+ // The specification says to call [[DefineOwnProperty]] with false for
+ // the Throw parameter, while Object.defineProperty uses true. For the
+ // calls here, the difference doesn't matter because we're adding
+ // properties to a new object.
+ _DefineDataProperty(options, "year", "numeric");
+ _DefineDataProperty(options, "month", "numeric");
+ _DefineDataProperty(options, "day", "numeric");
+ }
+
+ // Step 8.
+ if (needDefaults && (defaults === "time" || defaults === "all")) {
+ // See comment for step 7.
+ _DefineDataProperty(options, "hour", "numeric");
+ _DefineDataProperty(options, "minute", "numeric");
+ _DefineDataProperty(options, "second", "numeric");
+ }
+
+ // Step 9.
+ return options;
+}
+
+
+/**
+ * Compares the date and time components requested by options with the available
+ * date and time formats in formats, and selects the best match according
+ * to a specified basic matching algorithm.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 12.1.1.
+ */
+function BasicFormatMatcher(options, formats) {
+ // Steps 1-6.
+ var removalPenalty = 120,
+ additionPenalty = 20,
+ longLessPenalty = 8,
+ longMorePenalty = 6,
+ shortLessPenalty = 6,
+ shortMorePenalty = 3;
+
+ // Table 3.
+ var properties = ["weekday", "era", "year", "month", "day",
+ "hour", "minute", "second", "timeZoneName"];
+
+ // Step 11.c.vi.1.
+ var values = ["2-digit", "numeric", "narrow", "short", "long"];
+
+ // Steps 7-8.
+ var bestScore = -Infinity;
+ var bestFormat;
+
+ // Steps 9-11.
+ var i = 0;
+ var len = formats.length;
+ while (i < len) {
+ // Steps 11.a-b.
+ var format = formats[i];
+ var score = 0;
+
+ // Step 11.c.
+ var formatProp;
+ for (var j = 0; j < properties.length; j++) {
+ var property = properties[j];
+
+ // Step 11.c.i.
+ var optionsProp = options[property];
+ // Step missing from spec.
+ // https://bugs.ecmascript.org/show_bug.cgi?id=1254
+ formatProp = undefined;
+
+ // Steps 11.c.ii-iii.
+ if (callFunction(std_Object_hasOwnProperty, format, property))
+ formatProp = format[property];
+
+ if (optionsProp === undefined && formatProp !== undefined) {
+ // Step 11.c.iv.
+ score -= additionPenalty;
+ } else if (optionsProp !== undefined && formatProp === undefined) {
+ // Step 11.c.v.
+ score -= removalPenalty;
+ } else {
+ // Step 11.c.vi.
+ var optionsPropIndex = callFunction(ArrayIndexOf, values, optionsProp);
+ var formatPropIndex = callFunction(ArrayIndexOf, values, formatProp);
+ var delta = std_Math_max(std_Math_min(formatPropIndex - optionsPropIndex, 2), -2);
+ if (delta === 2)
+ score -= longMorePenalty;
+ else if (delta === 1)
+ score -= shortMorePenalty;
+ else if (delta === -1)
+ score -= shortLessPenalty;
+ else if (delta === -2)
+ score -= longLessPenalty;
+ }
+ }
+
+ // Step 11.d.
+ if (score > bestScore) {
+ bestScore = score;
+ bestFormat = format;
+ }
+
+ // Step 11.e.
+ i++;
+ }
+
+ // Step 12.
+ return bestFormat;
+}
+
+
+/**
+ * Compares the date and time components requested by options with the available
+ * date and time formats in formats, and selects the best match according
+ * to an unspecified best-fit matching algorithm.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 12.1.1.
+ */
+function BestFitFormatMatcher(options, formats) {
+ // this implementation doesn't have anything better
+ return BasicFormatMatcher(options, formats);
+}
+
+
+/**
+ * Returns the subset of the given locale list for which this locale list has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 12.2.2.
+ */
+function Intl_DateTimeFormat_supportedLocalesOf(locales /*, options*/) {
+ var options = arguments.length > 1 ? arguments[1] : undefined;
+
+ var availableLocales = callFunction(dateTimeFormatInternalProperties.availableLocales,
+ dateTimeFormatInternalProperties);
+ var requestedLocales = CanonicalizeLocaleList(locales);
+ return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
+
+/**
+ * DateTimeFormat internal properties.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.1 and 12.2.3.
+ */
+var dateTimeFormatInternalProperties = {
+ localeData: dateTimeFormatLocaleData,
+ _availableLocales: null,
+ availableLocales: function()
+ {
+ var locales = this._availableLocales;
+ if (locales)
+ return locales;
+
+ locales = intl_DateTimeFormat_availableLocales();
+ addSpecialMissingLanguageTags(locales);
+ return (this._availableLocales = locales);
+ },
+ relevantExtensionKeys: ["ca", "nu"]
+};
+
+
+function dateTimeFormatLocaleData(locale) {
+ return {
+ ca: intl_availableCalendars(locale),
+ nu: getNumberingSystems(locale)
+ };
+}
+
+
+/**
+ * Function to be bound and returned by Intl.DateTimeFormat.prototype.format.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 12.3.2.
+ */
+function dateTimeFormatFormatToBind() {
+ // Steps 1.a.i-ii
+ var date = arguments.length > 0 ? arguments[0] : undefined;
+ var x = (date === undefined) ? std_Date_now() : ToNumber(date);
+
+ // Step 1.a.iii.
+ return intl_FormatDateTime(this, x, false);
+}
+
+/**
+ * Returns a function bound to this DateTimeFormat that returns a String value
+ * representing the result of calling ToNumber(date) according to the
+ * effective locale and the formatting options of this DateTimeFormat.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 12.3.2.
+ */
+function Intl_DateTimeFormat_format_get() {
+ // Check "this DateTimeFormat object" per introduction of section 12.3.
+ var internals = getDateTimeFormatInternals(this, "format");
+
+ // Step 1.
+ if (internals.boundFormat === undefined) {
+ // Step 1.a.
+ var F = dateTimeFormatFormatToBind;
+
+ // Step 1.b-d.
+ var bf = callFunction(FunctionBind, F, this);
+ internals.boundFormat = bf;
+ }
+
+ // Step 2.
+ return internals.boundFormat;
+}
+
+
+function Intl_DateTimeFormat_formatToParts() {
+ // Check "this DateTimeFormat object" per introduction of section 12.3.
+ getDateTimeFormatInternals(this, "formatToParts");
+
+ // Steps 1.a.i-ii
+ var date = arguments.length > 0 ? arguments[0] : undefined;
+ var x = (date === undefined) ? std_Date_now() : ToNumber(date);
+
+ // Step 1.a.iii.
+ return intl_FormatDateTime(this, x, true);
+}
+
+
+/**
+ * Returns the resolved options for a DateTimeFormat object.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 12.3.3 and 12.4.
+ */
+function Intl_DateTimeFormat_resolvedOptions() {
+ // Check "this DateTimeFormat object" per introduction of section 12.3.
+ var internals = getDateTimeFormatInternals(this, "resolvedOptions");
+
+ var result = {
+ locale: internals.locale,
+ calendar: internals.calendar,
+ numberingSystem: internals.numberingSystem,
+ timeZone: internals.timeZone
+ };
+ resolveICUPattern(internals.pattern, result);
+ return result;
+}
+
+
+// Table mapping ICU pattern characters back to the corresponding date-time
+// components of DateTimeFormat. See
+// http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
+var icuPatternCharToComponent = {
+ E: "weekday",
+ G: "era",
+ y: "year",
+ M: "month",
+ L: "month",
+ d: "day",
+ h: "hour",
+ H: "hour",
+ k: "hour",
+ K: "hour",
+ m: "minute",
+ s: "second",
+ z: "timeZoneName",
+ v: "timeZoneName",
+ V: "timeZoneName"
+};
+
+
+/**
+ * Maps an ICU pattern string to a corresponding set of date-time components
+ * and their values, and adds properties for these components to the result
+ * object, which will be returned by the resolvedOptions method. For the
+ * interpretation of ICU pattern characters, see
+ * http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
+ */
+function resolveICUPattern(pattern, result) {
+ assert(IsObject(result), "resolveICUPattern");
+ var i = 0;
+ while (i < pattern.length) {
+ var c = pattern[i++];
+ if (c === "'") {
+ while (i < pattern.length && pattern[i] !== "'")
+ i++;
+ i++;
+ } else {
+ var count = 1;
+ while (i < pattern.length && pattern[i] === c) {
+ i++;
+ count++;
+ }
+ var value;
+ switch (c) {
+ // "text" cases
+ case "G":
+ case "E":
+ case "z":
+ case "v":
+ case "V":
+ if (count <= 3)
+ value = "short";
+ else if (count === 4)
+ value = "long";
+ else
+ value = "narrow";
+ break;
+ // "number" cases
+ case "y":
+ case "d":
+ case "h":
+ case "H":
+ case "m":
+ case "s":
+ case "k":
+ case "K":
+ if (count === 2)
+ value = "2-digit";
+ else
+ value = "numeric";
+ break;
+ // "text & number" cases
+ case "M":
+ case "L":
+ if (count === 1)
+ value = "numeric";
+ else if (count === 2)
+ value = "2-digit";
+ else if (count === 3)
+ value = "short";
+ else if (count === 4)
+ value = "long";
+ else
+ value = "narrow";
+ break;
+ default:
+ // skip other pattern characters and literal text
+ }
+ if (callFunction(std_Object_hasOwnProperty, icuPatternCharToComponent, c))
+ _DefineDataProperty(result, icuPatternCharToComponent[c], value);
+ if (c === "h" || c === "K")
+ _DefineDataProperty(result, "hour12", true);
+ else if (c === "H" || c === "k")
+ _DefineDataProperty(result, "hour12", false);
+ }
+ }
+}
+
+function Intl_getCanonicalLocales(locales) {
+ let codes = CanonicalizeLocaleList(locales);
+ let result = [];
+
+ let len = codes.length;
+ let k = 0;
+
+ while (k < len) {
+ _DefineDataProperty(result, k, codes[k]);
+ k++;
+ }
+ return result;
+}
+
+function Intl_getCalendarInfo(locales) {
+ const requestedLocales = CanonicalizeLocaleList(locales);
+
+ const DateTimeFormat = dateTimeFormatInternalProperties;
+ const localeData = DateTimeFormat.localeData;
+
+ const localeOpt = new Record();
+ localeOpt.localeMatcher = "best fit";
+
+ const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat),
+ requestedLocales,
+ localeOpt,
+ DateTimeFormat.relevantExtensionKeys,
+ localeData);
+
+ const result = intl_GetCalendarInfo(r.locale);
+ result.calendar = r.ca;
+ result.locale = r.locale;
+
+ return result;
+}
diff --git a/js/src/builtin/IntlData.js b/js/src/builtin/IntlData.js
new file mode 100644
index 000000000..269cf9f93
--- /dev/null
+++ b/js/src/builtin/IntlData.js
@@ -0,0 +1,382 @@
+// Generated by make_intl_data.py. DO NOT EDIT.
+
+// Mappings from complete tags to preferred values.
+// Derived from IANA Language Subtag Registry, file date 2016-10-12.
+// http://www.iana.org/assignments/language-subtag-registry
+var langTagMappings = {
+ "art-lojban": "jbo",
+ "cel-gaulish": "cel-gaulish",
+ "en-gb-oed": "en-GB-oxendict",
+ "i-ami": "ami",
+ "i-bnn": "bnn",
+ "i-default": "i-default",
+ "i-enochian": "i-enochian",
+ "i-hak": "hak",
+ "i-klingon": "tlh",
+ "i-lux": "lb",
+ "i-mingo": "i-mingo",
+ "i-navajo": "nv",
+ "i-pwn": "pwn",
+ "i-tao": "tao",
+ "i-tay": "tay",
+ "i-tsu": "tsu",
+ "ja-latn-hepburn-heploc": "ja-Latn-alalc97",
+ "no-bok": "nb",
+ "no-nyn": "nn",
+ "sgn-be-fr": "sfb",
+ "sgn-be-nl": "vgt",
+ "sgn-br": "bzs",
+ "sgn-ch-de": "sgg",
+ "sgn-co": "csn",
+ "sgn-de": "gsg",
+ "sgn-dk": "dsl",
+ "sgn-es": "ssp",
+ "sgn-fr": "fsl",
+ "sgn-gb": "bfi",
+ "sgn-gr": "gss",
+ "sgn-ie": "isg",
+ "sgn-it": "ise",
+ "sgn-jp": "jsl",
+ "sgn-mx": "mfs",
+ "sgn-ni": "ncs",
+ "sgn-nl": "dse",
+ "sgn-no": "nsl",
+ "sgn-pt": "psr",
+ "sgn-se": "swl",
+ "sgn-us": "ase",
+ "sgn-za": "sfs",
+ "zh-cmn": "cmn",
+ "zh-cmn-hans": "cmn-Hans",
+ "zh-cmn-hant": "cmn-Hant",
+ "zh-gan": "gan",
+ "zh-guoyu": "cmn",
+ "zh-hakka": "hak",
+ "zh-min": "zh-min",
+ "zh-min-nan": "nan",
+ "zh-wuu": "wuu",
+ "zh-xiang": "hsn",
+ "zh-yue": "yue",
+};
+
+// Mappings from non-extlang subtags to preferred values.
+// Derived from IANA Language Subtag Registry, file date 2016-10-12.
+// http://www.iana.org/assignments/language-subtag-registry
+var langSubtagMappings = {
+ "BU": "MM",
+ "DD": "DE",
+ "FX": "FR",
+ "TP": "TL",
+ "YD": "YE",
+ "ZR": "CD",
+ "aam": "aas",
+ "adp": "dz",
+ "aue": "ktz",
+ "ayx": "nun",
+ "bgm": "bcg",
+ "bjd": "drl",
+ "ccq": "rki",
+ "cjr": "mom",
+ "cka": "cmr",
+ "cmk": "xch",
+ "coy": "pij",
+ "cqu": "quh",
+ "drh": "khk",
+ "drw": "prs",
+ "gav": "dev",
+ "gfx": "vaj",
+ "ggn": "gvr",
+ "gti": "nyc",
+ "guv": "duz",
+ "hrr": "jal",
+ "ibi": "opa",
+ "ilw": "gal",
+ "in": "id",
+ "iw": "he",
+ "ji": "yi",
+ "jw": "jv",
+ "kgc": "tdf",
+ "kgh": "kml",
+ "koj": "kwv",
+ "ktr": "dtp",
+ "kvs": "gdj",
+ "kwq": "yam",
+ "kxe": "tvd",
+ "kzj": "dtp",
+ "kzt": "dtp",
+ "lii": "raq",
+ "lmm": "rmx",
+ "meg": "cir",
+ "mo": "ro",
+ "mst": "mry",
+ "mwj": "vaj",
+ "myt": "mry",
+ "nad": "xny",
+ "nnx": "ngv",
+ "nts": "pij",
+ "oun": "vaj",
+ "pcr": "adx",
+ "pmc": "huw",
+ "pmu": "phr",
+ "ppa": "bfy",
+ "ppr": "lcq",
+ "pry": "prt",
+ "puz": "pub",
+ "sca": "hle",
+ "tdu": "dtp",
+ "thc": "tpo",
+ "thx": "oyb",
+ "tie": "ras",
+ "tkk": "twm",
+ "tlw": "weo",
+ "tmp": "tyj",
+ "tne": "kak",
+ "tnf": "prs",
+ "tsf": "taj",
+ "uok": "ema",
+ "xba": "cax",
+ "xia": "acn",
+ "xkh": "waw",
+ "xsj": "suj",
+ "ybd": "rki",
+ "yma": "lrr",
+ "ymt": "mtm",
+ "yos": "zom",
+ "yuu": "yug",
+};
+
+// Mappings from extlang subtags to preferred values.
+// Derived from IANA Language Subtag Registry, file date 2016-10-12.
+// http://www.iana.org/assignments/language-subtag-registry
+var extlangMappings = {
+ "aao": {preferred: "aao", prefix: "ar"},
+ "abh": {preferred: "abh", prefix: "ar"},
+ "abv": {preferred: "abv", prefix: "ar"},
+ "acm": {preferred: "acm", prefix: "ar"},
+ "acq": {preferred: "acq", prefix: "ar"},
+ "acw": {preferred: "acw", prefix: "ar"},
+ "acx": {preferred: "acx", prefix: "ar"},
+ "acy": {preferred: "acy", prefix: "ar"},
+ "adf": {preferred: "adf", prefix: "ar"},
+ "ads": {preferred: "ads", prefix: "sgn"},
+ "aeb": {preferred: "aeb", prefix: "ar"},
+ "aec": {preferred: "aec", prefix: "ar"},
+ "aed": {preferred: "aed", prefix: "sgn"},
+ "aen": {preferred: "aen", prefix: "sgn"},
+ "afb": {preferred: "afb", prefix: "ar"},
+ "afg": {preferred: "afg", prefix: "sgn"},
+ "ajp": {preferred: "ajp", prefix: "ar"},
+ "apc": {preferred: "apc", prefix: "ar"},
+ "apd": {preferred: "apd", prefix: "ar"},
+ "arb": {preferred: "arb", prefix: "ar"},
+ "arq": {preferred: "arq", prefix: "ar"},
+ "ars": {preferred: "ars", prefix: "ar"},
+ "ary": {preferred: "ary", prefix: "ar"},
+ "arz": {preferred: "arz", prefix: "ar"},
+ "ase": {preferred: "ase", prefix: "sgn"},
+ "asf": {preferred: "asf", prefix: "sgn"},
+ "asp": {preferred: "asp", prefix: "sgn"},
+ "asq": {preferred: "asq", prefix: "sgn"},
+ "asw": {preferred: "asw", prefix: "sgn"},
+ "auz": {preferred: "auz", prefix: "ar"},
+ "avl": {preferred: "avl", prefix: "ar"},
+ "ayh": {preferred: "ayh", prefix: "ar"},
+ "ayl": {preferred: "ayl", prefix: "ar"},
+ "ayn": {preferred: "ayn", prefix: "ar"},
+ "ayp": {preferred: "ayp", prefix: "ar"},
+ "bbz": {preferred: "bbz", prefix: "ar"},
+ "bfi": {preferred: "bfi", prefix: "sgn"},
+ "bfk": {preferred: "bfk", prefix: "sgn"},
+ "bjn": {preferred: "bjn", prefix: "ms"},
+ "bog": {preferred: "bog", prefix: "sgn"},
+ "bqn": {preferred: "bqn", prefix: "sgn"},
+ "bqy": {preferred: "bqy", prefix: "sgn"},
+ "btj": {preferred: "btj", prefix: "ms"},
+ "bve": {preferred: "bve", prefix: "ms"},
+ "bvl": {preferred: "bvl", prefix: "sgn"},
+ "bvu": {preferred: "bvu", prefix: "ms"},
+ "bzs": {preferred: "bzs", prefix: "sgn"},
+ "cdo": {preferred: "cdo", prefix: "zh"},
+ "cds": {preferred: "cds", prefix: "sgn"},
+ "cjy": {preferred: "cjy", prefix: "zh"},
+ "cmn": {preferred: "cmn", prefix: "zh"},
+ "coa": {preferred: "coa", prefix: "ms"},
+ "cpx": {preferred: "cpx", prefix: "zh"},
+ "csc": {preferred: "csc", prefix: "sgn"},
+ "csd": {preferred: "csd", prefix: "sgn"},
+ "cse": {preferred: "cse", prefix: "sgn"},
+ "csf": {preferred: "csf", prefix: "sgn"},
+ "csg": {preferred: "csg", prefix: "sgn"},
+ "csl": {preferred: "csl", prefix: "sgn"},
+ "csn": {preferred: "csn", prefix: "sgn"},
+ "csq": {preferred: "csq", prefix: "sgn"},
+ "csr": {preferred: "csr", prefix: "sgn"},
+ "czh": {preferred: "czh", prefix: "zh"},
+ "czo": {preferred: "czo", prefix: "zh"},
+ "doq": {preferred: "doq", prefix: "sgn"},
+ "dse": {preferred: "dse", prefix: "sgn"},
+ "dsl": {preferred: "dsl", prefix: "sgn"},
+ "dup": {preferred: "dup", prefix: "ms"},
+ "ecs": {preferred: "ecs", prefix: "sgn"},
+ "esl": {preferred: "esl", prefix: "sgn"},
+ "esn": {preferred: "esn", prefix: "sgn"},
+ "eso": {preferred: "eso", prefix: "sgn"},
+ "eth": {preferred: "eth", prefix: "sgn"},
+ "fcs": {preferred: "fcs", prefix: "sgn"},
+ "fse": {preferred: "fse", prefix: "sgn"},
+ "fsl": {preferred: "fsl", prefix: "sgn"},
+ "fss": {preferred: "fss", prefix: "sgn"},
+ "gan": {preferred: "gan", prefix: "zh"},
+ "gds": {preferred: "gds", prefix: "sgn"},
+ "gom": {preferred: "gom", prefix: "kok"},
+ "gse": {preferred: "gse", prefix: "sgn"},
+ "gsg": {preferred: "gsg", prefix: "sgn"},
+ "gsm": {preferred: "gsm", prefix: "sgn"},
+ "gss": {preferred: "gss", prefix: "sgn"},
+ "gus": {preferred: "gus", prefix: "sgn"},
+ "hab": {preferred: "hab", prefix: "sgn"},
+ "haf": {preferred: "haf", prefix: "sgn"},
+ "hak": {preferred: "hak", prefix: "zh"},
+ "hds": {preferred: "hds", prefix: "sgn"},
+ "hji": {preferred: "hji", prefix: "ms"},
+ "hks": {preferred: "hks", prefix: "sgn"},
+ "hos": {preferred: "hos", prefix: "sgn"},
+ "hps": {preferred: "hps", prefix: "sgn"},
+ "hsh": {preferred: "hsh", prefix: "sgn"},
+ "hsl": {preferred: "hsl", prefix: "sgn"},
+ "hsn": {preferred: "hsn", prefix: "zh"},
+ "icl": {preferred: "icl", prefix: "sgn"},
+ "iks": {preferred: "iks", prefix: "sgn"},
+ "ils": {preferred: "ils", prefix: "sgn"},
+ "inl": {preferred: "inl", prefix: "sgn"},
+ "ins": {preferred: "ins", prefix: "sgn"},
+ "ise": {preferred: "ise", prefix: "sgn"},
+ "isg": {preferred: "isg", prefix: "sgn"},
+ "isr": {preferred: "isr", prefix: "sgn"},
+ "jak": {preferred: "jak", prefix: "ms"},
+ "jax": {preferred: "jax", prefix: "ms"},
+ "jcs": {preferred: "jcs", prefix: "sgn"},
+ "jhs": {preferred: "jhs", prefix: "sgn"},
+ "jls": {preferred: "jls", prefix: "sgn"},
+ "jos": {preferred: "jos", prefix: "sgn"},
+ "jsl": {preferred: "jsl", prefix: "sgn"},
+ "jus": {preferred: "jus", prefix: "sgn"},
+ "kgi": {preferred: "kgi", prefix: "sgn"},
+ "knn": {preferred: "knn", prefix: "kok"},
+ "kvb": {preferred: "kvb", prefix: "ms"},
+ "kvk": {preferred: "kvk", prefix: "sgn"},
+ "kvr": {preferred: "kvr", prefix: "ms"},
+ "kxd": {preferred: "kxd", prefix: "ms"},
+ "lbs": {preferred: "lbs", prefix: "sgn"},
+ "lce": {preferred: "lce", prefix: "ms"},
+ "lcf": {preferred: "lcf", prefix: "ms"},
+ "liw": {preferred: "liw", prefix: "ms"},
+ "lls": {preferred: "lls", prefix: "sgn"},
+ "lsg": {preferred: "lsg", prefix: "sgn"},
+ "lsl": {preferred: "lsl", prefix: "sgn"},
+ "lso": {preferred: "lso", prefix: "sgn"},
+ "lsp": {preferred: "lsp", prefix: "sgn"},
+ "lst": {preferred: "lst", prefix: "sgn"},
+ "lsy": {preferred: "lsy", prefix: "sgn"},
+ "ltg": {preferred: "ltg", prefix: "lv"},
+ "lvs": {preferred: "lvs", prefix: "lv"},
+ "lzh": {preferred: "lzh", prefix: "zh"},
+ "max": {preferred: "max", prefix: "ms"},
+ "mdl": {preferred: "mdl", prefix: "sgn"},
+ "meo": {preferred: "meo", prefix: "ms"},
+ "mfa": {preferred: "mfa", prefix: "ms"},
+ "mfb": {preferred: "mfb", prefix: "ms"},
+ "mfs": {preferred: "mfs", prefix: "sgn"},
+ "min": {preferred: "min", prefix: "ms"},
+ "mnp": {preferred: "mnp", prefix: "zh"},
+ "mqg": {preferred: "mqg", prefix: "ms"},
+ "mre": {preferred: "mre", prefix: "sgn"},
+ "msd": {preferred: "msd", prefix: "sgn"},
+ "msi": {preferred: "msi", prefix: "ms"},
+ "msr": {preferred: "msr", prefix: "sgn"},
+ "mui": {preferred: "mui", prefix: "ms"},
+ "mzc": {preferred: "mzc", prefix: "sgn"},
+ "mzg": {preferred: "mzg", prefix: "sgn"},
+ "mzy": {preferred: "mzy", prefix: "sgn"},
+ "nan": {preferred: "nan", prefix: "zh"},
+ "nbs": {preferred: "nbs", prefix: "sgn"},
+ "ncs": {preferred: "ncs", prefix: "sgn"},
+ "nsi": {preferred: "nsi", prefix: "sgn"},
+ "nsl": {preferred: "nsl", prefix: "sgn"},
+ "nsp": {preferred: "nsp", prefix: "sgn"},
+ "nsr": {preferred: "nsr", prefix: "sgn"},
+ "nzs": {preferred: "nzs", prefix: "sgn"},
+ "okl": {preferred: "okl", prefix: "sgn"},
+ "orn": {preferred: "orn", prefix: "ms"},
+ "ors": {preferred: "ors", prefix: "ms"},
+ "pel": {preferred: "pel", prefix: "ms"},
+ "pga": {preferred: "pga", prefix: "ar"},
+ "pgz": {preferred: "pgz", prefix: "sgn"},
+ "pks": {preferred: "pks", prefix: "sgn"},
+ "prl": {preferred: "prl", prefix: "sgn"},
+ "prz": {preferred: "prz", prefix: "sgn"},
+ "psc": {preferred: "psc", prefix: "sgn"},
+ "psd": {preferred: "psd", prefix: "sgn"},
+ "pse": {preferred: "pse", prefix: "ms"},
+ "psg": {preferred: "psg", prefix: "sgn"},
+ "psl": {preferred: "psl", prefix: "sgn"},
+ "pso": {preferred: "pso", prefix: "sgn"},
+ "psp": {preferred: "psp", prefix: "sgn"},
+ "psr": {preferred: "psr", prefix: "sgn"},
+ "pys": {preferred: "pys", prefix: "sgn"},
+ "rms": {preferred: "rms", prefix: "sgn"},
+ "rsi": {preferred: "rsi", prefix: "sgn"},
+ "rsl": {preferred: "rsl", prefix: "sgn"},
+ "rsm": {preferred: "rsm", prefix: "sgn"},
+ "sdl": {preferred: "sdl", prefix: "sgn"},
+ "sfb": {preferred: "sfb", prefix: "sgn"},
+ "sfs": {preferred: "sfs", prefix: "sgn"},
+ "sgg": {preferred: "sgg", prefix: "sgn"},
+ "sgx": {preferred: "sgx", prefix: "sgn"},
+ "shu": {preferred: "shu", prefix: "ar"},
+ "slf": {preferred: "slf", prefix: "sgn"},
+ "sls": {preferred: "sls", prefix: "sgn"},
+ "sqk": {preferred: "sqk", prefix: "sgn"},
+ "sqs": {preferred: "sqs", prefix: "sgn"},
+ "ssh": {preferred: "ssh", prefix: "ar"},
+ "ssp": {preferred: "ssp", prefix: "sgn"},
+ "ssr": {preferred: "ssr", prefix: "sgn"},
+ "svk": {preferred: "svk", prefix: "sgn"},
+ "swc": {preferred: "swc", prefix: "sw"},
+ "swh": {preferred: "swh", prefix: "sw"},
+ "swl": {preferred: "swl", prefix: "sgn"},
+ "syy": {preferred: "syy", prefix: "sgn"},
+ "tmw": {preferred: "tmw", prefix: "ms"},
+ "tse": {preferred: "tse", prefix: "sgn"},
+ "tsm": {preferred: "tsm", prefix: "sgn"},
+ "tsq": {preferred: "tsq", prefix: "sgn"},
+ "tss": {preferred: "tss", prefix: "sgn"},
+ "tsy": {preferred: "tsy", prefix: "sgn"},
+ "tza": {preferred: "tza", prefix: "sgn"},
+ "ugn": {preferred: "ugn", prefix: "sgn"},
+ "ugy": {preferred: "ugy", prefix: "sgn"},
+ "ukl": {preferred: "ukl", prefix: "sgn"},
+ "uks": {preferred: "uks", prefix: "sgn"},
+ "urk": {preferred: "urk", prefix: "ms"},
+ "uzn": {preferred: "uzn", prefix: "uz"},
+ "uzs": {preferred: "uzs", prefix: "uz"},
+ "vgt": {preferred: "vgt", prefix: "sgn"},
+ "vkk": {preferred: "vkk", prefix: "ms"},
+ "vkt": {preferred: "vkt", prefix: "ms"},
+ "vsi": {preferred: "vsi", prefix: "sgn"},
+ "vsl": {preferred: "vsl", prefix: "sgn"},
+ "vsv": {preferred: "vsv", prefix: "sgn"},
+ "wuu": {preferred: "wuu", prefix: "zh"},
+ "xki": {preferred: "xki", prefix: "sgn"},
+ "xml": {preferred: "xml", prefix: "sgn"},
+ "xmm": {preferred: "xmm", prefix: "ms"},
+ "xms": {preferred: "xms", prefix: "sgn"},
+ "ygs": {preferred: "ygs", prefix: "sgn"},
+ "yhs": {preferred: "yhs", prefix: "sgn"},
+ "ysl": {preferred: "ysl", prefix: "sgn"},
+ "yue": {preferred: "yue", prefix: "zh"},
+ "zib": {preferred: "zib", prefix: "sgn"},
+ "zlm": {preferred: "zlm", prefix: "ms"},
+ "zmi": {preferred: "zmi", prefix: "ms"},
+ "zsl": {preferred: "zsl", prefix: "sgn"},
+ "zsm": {preferred: "zsm", prefix: "ms"},
+};
diff --git a/js/src/builtin/IntlTimeZoneData.h b/js/src/builtin/IntlTimeZoneData.h
new file mode 100644
index 000000000..bb7d22109
--- /dev/null
+++ b/js/src/builtin/IntlTimeZoneData.h
@@ -0,0 +1,137 @@
+// Generated by make_intl_data.py. DO NOT EDIT.
+// tzdata version = 2017c
+
+#ifndef builtin_IntlTimeZoneData_h
+#define builtin_IntlTimeZoneData_h
+
+namespace js {
+namespace timezone {
+
+// Format:
+// "ZoneName" // ICU-Name [time zone file]
+const char* const ianaZonesTreatedAsLinksByICU[] = {
+ "Africa/Asmara", // Africa/Asmera [backzone]
+ "Africa/Timbuktu", // Africa/Bamako [backzone]
+ "America/Argentina/Buenos_Aires", // America/Buenos_Aires [southamerica]
+ "America/Argentina/Catamarca", // America/Catamarca [southamerica]
+ "America/Argentina/ComodRivadavia", // America/Catamarca [backzone]
+ "America/Argentina/Cordoba", // America/Cordoba [southamerica]
+ "America/Argentina/Jujuy", // America/Jujuy [southamerica]
+ "America/Argentina/Mendoza", // America/Mendoza [southamerica]
+ "America/Atikokan", // America/Coral_Harbour [northamerica]
+ "America/Ensenada", // America/Tijuana [backzone]
+ "America/Indiana/Indianapolis", // America/Indianapolis [northamerica]
+ "America/Kentucky/Louisville", // America/Louisville [northamerica]
+ "America/Rosario", // America/Cordoba [backzone]
+ "Asia/Chongqing", // Asia/Shanghai [backzone]
+ "Asia/Harbin", // Asia/Shanghai [backzone]
+ "Asia/Ho_Chi_Minh", // Asia/Saigon [asia]
+ "Asia/Kashgar", // Asia/Urumqi [backzone]
+ "Asia/Kathmandu", // Asia/Katmandu [asia]
+ "Asia/Kolkata", // Asia/Calcutta [asia]
+ "Asia/Tel_Aviv", // Asia/Jerusalem [backzone]
+ "Asia/Yangon", // Asia/Rangoon [asia]
+ "Atlantic/Faroe", // Atlantic/Faeroe [europe]
+ "Atlantic/Jan_Mayen", // Arctic/Longyearbyen [backzone]
+ "EST", // Etc/GMT+5 [northamerica]
+ "Europe/Belfast", // Europe/London [backzone]
+ "Europe/Tiraspol", // Europe/Chisinau [backzone]
+ "HST", // Etc/GMT+10 [northamerica]
+ "MST", // Etc/GMT+7 [northamerica]
+ "Pacific/Chuuk", // Pacific/Truk [australasia]
+ "Pacific/Pohnpei", // Pacific/Ponape [australasia]
+};
+
+// Format:
+// "LinkName", "Target" // ICU-Target [time zone file]
+struct LinkAndTarget
+{
+ const char* const link;
+ const char* const target;
+};
+
+const LinkAndTarget ianaLinksCanonicalizedDifferentlyByICU[] = {
+ { "Africa/Asmera", "Africa/Asmara" }, // Africa/Asmera [backward]
+ { "America/Buenos_Aires", "America/Argentina/Buenos_Aires" }, // America/Buenos_Aires [backward]
+ { "America/Catamarca", "America/Argentina/Catamarca" }, // America/Catamarca [backward]
+ { "America/Cordoba", "America/Argentina/Cordoba" }, // America/Cordoba [backward]
+ { "America/Fort_Wayne", "America/Indiana/Indianapolis" }, // America/Indianapolis [backward]
+ { "America/Indianapolis", "America/Indiana/Indianapolis" }, // America/Indianapolis [backward]
+ { "America/Jujuy", "America/Argentina/Jujuy" }, // America/Jujuy [backward]
+ { "America/Kralendijk", "America/Curacao" }, // America/Kralendijk [southamerica]
+ { "America/Louisville", "America/Kentucky/Louisville" }, // America/Louisville [backward]
+ { "America/Lower_Princes", "America/Curacao" }, // America/Lower_Princes [southamerica]
+ { "America/Marigot", "America/Port_of_Spain" }, // America/Marigot [southamerica]
+ { "America/Mendoza", "America/Argentina/Mendoza" }, // America/Mendoza [backward]
+ { "America/Santa_Isabel", "America/Tijuana" }, // America/Santa_Isabel [backward]
+ { "America/St_Barthelemy", "America/Port_of_Spain" }, // America/St_Barthelemy [southamerica]
+ { "America/Virgin", "America/Port_of_Spain" }, // America/St_Thomas [backward]
+ { "Antarctica/South_Pole", "Antarctica/McMurdo" }, // Pacific/Auckland [backward]
+ { "Arctic/Longyearbyen", "Europe/Oslo" }, // Arctic/Longyearbyen [europe]
+ { "Asia/Calcutta", "Asia/Kolkata" }, // Asia/Calcutta [backward]
+ { "Asia/Chungking", "Asia/Chongqing" }, // Asia/Shanghai [backward]
+ { "Asia/Katmandu", "Asia/Kathmandu" }, // Asia/Katmandu [backward]
+ { "Asia/Rangoon", "Asia/Yangon" }, // Asia/Rangoon [backward]
+ { "Asia/Saigon", "Asia/Ho_Chi_Minh" }, // Asia/Saigon [backward]
+ { "Atlantic/Faeroe", "Atlantic/Faroe" }, // Atlantic/Faeroe [backward]
+ { "Europe/Bratislava", "Europe/Prague" }, // Europe/Bratislava [europe]
+ { "Europe/Busingen", "Europe/Zurich" }, // Europe/Busingen [europe]
+ { "Europe/Mariehamn", "Europe/Helsinki" }, // Europe/Mariehamn [europe]
+ { "Europe/Podgorica", "Europe/Belgrade" }, // Europe/Podgorica [europe]
+ { "Europe/San_Marino", "Europe/Rome" }, // Europe/San_Marino [europe]
+ { "Europe/Vatican", "Europe/Rome" }, // Europe/Vatican [europe]
+ { "Pacific/Ponape", "Pacific/Pohnpei" }, // Pacific/Ponape [backward]
+ { "Pacific/Truk", "Pacific/Chuuk" }, // Pacific/Truk [backward]
+ { "Pacific/Yap", "Pacific/Chuuk" }, // Pacific/Truk [backward]
+ { "US/East-Indiana", "America/Indiana/Indianapolis" }, // America/Indianapolis [backward]
+};
+
+// Legacy ICU time zones, these are not valid IANA time zone names. We also
+// disallow the old and deprecated System V time zones.
+// https://ssl.icu-project.org/repos/icu/trunk/icu4c/source/tools/tzcode/icuzones
+const char* const legacyICUTimeZones[] = {
+ "ACT",
+ "AET",
+ "AGT",
+ "ART",
+ "AST",
+ "BET",
+ "BST",
+ "CAT",
+ "CNT",
+ "CST",
+ "CTT",
+ "Canada/East-Saskatchewan",
+ "EAT",
+ "ECT",
+ "IET",
+ "IST",
+ "JST",
+ "MIT",
+ "NET",
+ "NST",
+ "PLT",
+ "PNT",
+ "PRT",
+ "PST",
+ "SST",
+ "VST",
+ "SystemV/AST4",
+ "SystemV/AST4ADT",
+ "SystemV/CST6",
+ "SystemV/CST6CDT",
+ "SystemV/EST5",
+ "SystemV/EST5EDT",
+ "SystemV/HST10",
+ "SystemV/MST7",
+ "SystemV/MST7MDT",
+ "SystemV/PST8",
+ "SystemV/PST8PDT",
+ "SystemV/YST9",
+ "SystemV/YST9YDT",
+};
+
+} // namespace timezone
+} // namespace js
+
+#endif /* builtin_IntlTimeZoneData_h */
diff --git a/js/src/builtin/Iterator.js b/js/src/builtin/Iterator.js
new file mode 100644
index 000000000..735eec7a0
--- /dev/null
+++ b/js/src/builtin/Iterator.js
@@ -0,0 +1,127 @@
+/* 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/. */
+
+function IteratorIdentity() {
+ return this;
+}
+
+var LegacyIteratorWrapperMap = new std_WeakMap();
+
+function LegacyIteratorNext(arg) {
+ var iter = callFunction(std_WeakMap_get, LegacyIteratorWrapperMap, this);
+ try {
+ return { value: callContentFunction(iter.next, iter, arg), done: false };
+ } catch (e) {
+ if (e instanceof std_StopIteration)
+ return { value: undefined, done: true };
+ throw e;
+ }
+}
+
+function LegacyIteratorThrow(exn) {
+ var iter = callFunction(std_WeakMap_get, LegacyIteratorWrapperMap, this);
+ try {
+ return { value: callContentFunction(iter.throw, iter, exn), done: false };
+ } catch (e) {
+ if (e instanceof std_StopIteration)
+ return { value: undefined, done: true };
+ throw e;
+ }
+}
+
+function LegacyIterator(iter) {
+ callFunction(std_WeakMap_set, LegacyIteratorWrapperMap, this, iter);
+}
+
+function LegacyGeneratorIterator(iter) {
+ callFunction(std_WeakMap_set, LegacyIteratorWrapperMap, this, iter);
+}
+
+var LegacyIteratorsInitialized = std_Object_create(null);
+
+function InitLegacyIterators() {
+ var props = std_Object_create(null);
+
+ props.next = std_Object_create(null);
+ props.next.value = LegacyIteratorNext;
+ props.next.enumerable = false;
+ props.next.configurable = true;
+ props.next.writable = true;
+
+ props[std_iterator] = std_Object_create(null);
+ props[std_iterator].value = IteratorIdentity;
+ props[std_iterator].enumerable = false;
+ props[std_iterator].configurable = true;
+ props[std_iterator].writable = true;
+
+ var LegacyIteratorProto = std_Object_create(GetIteratorPrototype(), props);
+ MakeConstructible(LegacyIterator, LegacyIteratorProto);
+
+ props.throw = std_Object_create(null);
+ props.throw.value = LegacyIteratorThrow;
+ props.throw.enumerable = false;
+ props.throw.configurable = true;
+ props.throw.writable = true;
+
+ var LegacyGeneratorIteratorProto = std_Object_create(GetIteratorPrototype(), props);
+ MakeConstructible(LegacyGeneratorIterator, LegacyGeneratorIteratorProto);
+
+ LegacyIteratorsInitialized.initialized = true;
+}
+
+function NewLegacyIterator(iter, wrapper) {
+ if (!LegacyIteratorsInitialized.initialized)
+ InitLegacyIterators();
+
+ return new wrapper(iter);
+}
+
+function LegacyIteratorShim() {
+ return NewLegacyIterator(ToObject(this), LegacyIterator);
+}
+
+function LegacyGeneratorIteratorShim() {
+ return NewLegacyIterator(ToObject(this), LegacyGeneratorIterator);
+}
+
+// 7.4.8 CreateListIterator()
+function CreateListIterator(array) {
+ let iterator = NewListIterator();
+ UnsafeSetReservedSlot(iterator, ITERATOR_SLOT_TARGET, array);
+ UnsafeSetReservedSlot(iterator, ITERATOR_SLOT_NEXT_INDEX, 0);
+
+ // 7.4.8.1 ListIterator next()
+ // The spec requires that we use a new next function per iterator object.
+ let next = function() {
+ if (!IsObject(this) || !IsListIterator(this))
+ return callFunction(CallListIteratorMethodIfWrapped, this, "ListIteratorNext");
+
+ if (ActiveFunction() !== UnsafeGetReservedSlot(this, ITERATOR_SLOT_NEXT_METHOD))
+ ThrowTypeError(JSMSG_INCOMPATIBLE_METHOD, "next", "method", ToString(this));
+
+ let array = UnsafeGetObjectFromReservedSlot(this, ITERATOR_SLOT_TARGET);
+ let index = UnsafeGetReservedSlot(this, ITERATOR_SLOT_NEXT_INDEX);
+
+ if (index >= ToLength(array.length)) {
+ UnsafeSetReservedSlot(this, ITERATOR_SLOT_NEXT_INDEX, 1/0);
+ return { value: undefined, done: true };
+ }
+
+ UnsafeSetReservedSlot(this, ITERATOR_SLOT_NEXT_INDEX, index + 1);
+ return { value: array[index], done: false };
+ };
+
+ UnsafeSetReservedSlot(iterator, ITERATOR_SLOT_NEXT_METHOD, next);
+ iterator.next = next;
+
+ iterator[std_iterator] = ListIteratorIdentity;
+ return iterator;
+}
+
+function ListIteratorIdentity() {
+ if (!IsObject(this) || !IsListIterator(this))
+ return callFunction(CallListIteratorMethodIfWrapped, this, "ListIteratorIdentity");
+
+ return this;
+}
diff --git a/js/src/builtin/Map.js b/js/src/builtin/Map.js
new file mode 100644
index 000000000..432364614
--- /dev/null
+++ b/js/src/builtin/Map.js
@@ -0,0 +1,138 @@
+/* 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/. */
+
+// ES2017 draft rev 0e10c9f29fca1385980c08a7d5e7bb3eb775e2e4
+// 23.1.1.1 Map, steps 6-8
+function MapConstructorInit(iterable) {
+ var map = this;
+
+ // Step 6.a.
+ var adder = map.set;
+
+ // Step 6.b.
+ if (!IsCallable(adder))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, typeof adder);
+
+ // Step 6.c.
+ var iterFn = iterable[std_iterator];
+ if (!IsCallable(iterFn))
+ ThrowTypeError(JSMSG_NOT_ITERABLE, DecompileArg(0, iterable));
+
+ var iter = callContentFunction(iterFn, iterable);
+ if (!IsObject(iter))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof iter);
+
+ // Step 7 (not applicable).
+
+ // Step 8.
+ while (true) {
+ // Step 8.a.
+ var next = callContentFunction(iter.next, iter);
+ if (!IsObject(next))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof next);
+
+ // Step 8.b.
+ if (next.done)
+ return;
+
+ // Step 8.c.
+ var nextItem = next.value;
+
+ // Step 8.d.
+ if (!IsObject(nextItem))
+ ThrowTypeError(JSMSG_INVALID_MAP_ITERABLE, "Map");
+
+ // Steps 8.e-j.
+ callContentFunction(adder, map, nextItem[0], nextItem[1]);
+ }
+}
+
+/* ES6 20121122 draft 15.14.4.4. */
+function MapForEach(callbackfn, thisArg = undefined) {
+ /* Step 1-2. */
+ var M = this;
+ if (!IsObject(M))
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "Map", "forEach", typeof M);
+
+ /* Step 3-4. */
+ try {
+ callFunction(std_Map_has, M);
+ } catch (e) {
+ // has will throw on non-Map objects, throw our own error in that case.
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "Map", "forEach", typeof M);
+ }
+
+ /* Step 5. */
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ /* Step 6-8. */
+ var entries = callFunction(std_Map_iterator, M);
+ while (true) {
+ var result = callFunction(MapIteratorNext, entries);
+ if (result.done)
+ break;
+ var entry = result.value;
+ callContentFunction(callbackfn, thisArg, entry[1], entry[0], M);
+ }
+}
+
+var iteratorTemp = { mapIterationResultPair : null };
+
+function MapIteratorNext() {
+ // Step 1.
+ var O = this;
+
+ // Steps 2-3.
+ if (!IsObject(O) || !IsMapIterator(O))
+ return callFunction(CallMapIteratorMethodIfWrapped, O, "MapIteratorNext");
+
+ // Steps 4-5 (implemented in _GetNextMapEntryForIterator).
+ // Steps 8-9 (omitted).
+
+ var mapIterationResultPair = iteratorTemp.mapIterationResultPair;
+ if (!mapIterationResultPair) {
+ mapIterationResultPair = iteratorTemp.mapIterationResultPair =
+ _CreateMapIterationResultPair();
+ }
+
+ var retVal = {value: undefined, done: true};
+
+ // Step 10.a, 11.
+ var done = _GetNextMapEntryForIterator(O, mapIterationResultPair);
+ if (!done) {
+ // Steps 10.b-c (omitted).
+
+ // Step 6.
+ var itemKind = UnsafeGetInt32FromReservedSlot(this, ITERATOR_SLOT_ITEM_KIND);
+
+ var result;
+ if (itemKind === ITEM_KIND_KEY) {
+ // Step 10.d.i.
+ result = mapIterationResultPair[0];
+ } else if (itemKind === ITEM_KIND_VALUE) {
+ // Step 10.d.ii.
+ result = mapIterationResultPair[1];
+ } else {
+ // Step 10.d.iii.
+ assert(itemKind === ITEM_KIND_KEY_AND_VALUE, itemKind);
+ result = [mapIterationResultPair[0], mapIterationResultPair[1]];
+ }
+
+ mapIterationResultPair[0] = null;
+ mapIterationResultPair[1] = null;
+ retVal.value = result;
+ retVal.done = false;
+ }
+
+ // Steps 7, 12.
+ return retVal;
+}
+
+// ES6 final draft 23.1.2.2.
+function MapSpecies() {
+ // Step 1.
+ return this;
+}
+_SetCanonicalName(MapSpecies, "get [Symbol.species]");
diff --git a/js/src/builtin/MapObject.cpp b/js/src/builtin/MapObject.cpp
new file mode 100644
index 000000000..c496cfb77
--- /dev/null
+++ b/js/src/builtin/MapObject.cpp
@@ -0,0 +1,1751 @@
+/* -*- 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 "builtin/MapObject.h"
+
+#include "jscntxt.h"
+#include "jsiter.h"
+#include "jsobj.h"
+
+#include "ds/OrderedHashTable.h"
+#include "gc/Marking.h"
+#include "js/Utility.h"
+#include "vm/GlobalObject.h"
+#include "vm/Interpreter.h"
+#include "vm/SelfHosting.h"
+#include "vm/Symbol.h"
+
+#include "jsobjinlines.h"
+
+#include "vm/Interpreter-inl.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+using mozilla::ArrayLength;
+using mozilla::IsNaN;
+using mozilla::NumberEqualsInt32;
+
+using JS::DoubleNaNValue;
+using JS::ForOfIterator;
+
+
+/*** HashableValue *******************************************************************************/
+
+bool
+HashableValue::setValue(JSContext* cx, HandleValue v)
+{
+ if (v.isString()) {
+ // Atomize so that hash() and operator==() are fast and infallible.
+ JSString* str = AtomizeString(cx, v.toString(), DoNotPinAtom);
+ if (!str)
+ return false;
+ value = StringValue(str);
+ } else if (v.isDouble()) {
+ double d = v.toDouble();
+ int32_t i;
+ if (NumberEqualsInt32(d, &i)) {
+ // Normalize int32_t-valued doubles to int32_t for faster hashing and testing.
+ value = Int32Value(i);
+ } else if (IsNaN(d)) {
+ // NaNs with different bits must hash and test identically.
+ value = DoubleNaNValue();
+ } else {
+ value = v;
+ }
+ } else {
+ value = v;
+ }
+
+ MOZ_ASSERT(value.isUndefined() || value.isNull() || value.isBoolean() || value.isNumber() ||
+ value.isString() || value.isSymbol() || value.isObject());
+ return true;
+}
+
+static HashNumber
+HashValue(const Value& v, const mozilla::HashCodeScrambler& hcs)
+{
+ // HashableValue::setValue normalizes values so that the SameValue relation
+ // on HashableValues is the same as the == relationship on
+ // value.asRawBits(). So why not just return that? Security.
+ //
+ // To avoid revealing GC of atoms, string-based hash codes are computed
+ // from the string contents rather than any pointer; to avoid revealing
+ // addresses, pointer-based hash codes are computed using the
+ // HashCodeScrambler.
+
+ if (v.isString())
+ return v.toString()->asAtom().hash();
+ if (v.isSymbol())
+ return v.toSymbol()->hash();
+ if (v.isObject())
+ return hcs.scramble(v.asRawBits());
+
+ MOZ_ASSERT(v.isNull() || !v.isGCThing(), "do not reveal pointers via hash codes");
+ return v.asRawBits();
+}
+
+HashNumber
+HashableValue::hash(const mozilla::HashCodeScrambler& hcs) const
+{
+ return HashValue(value, hcs);
+}
+
+bool
+HashableValue::operator==(const HashableValue& other) const
+{
+ // Two HashableValues are equal if they have equal bits.
+ bool b = (value.asRawBits() == other.value.asRawBits());
+
+#ifdef DEBUG
+ bool same;
+ JS::RootingContext* rcx = GetJSContextFromMainThread();
+ RootedValue valueRoot(rcx, value);
+ RootedValue otherRoot(rcx, other.value);
+ MOZ_ASSERT(SameValue(nullptr, valueRoot, otherRoot, &same));
+ MOZ_ASSERT(same == b);
+#endif
+ return b;
+}
+
+HashableValue
+HashableValue::mark(JSTracer* trc) const
+{
+ HashableValue hv(*this);
+ TraceEdge(trc, &hv.value, "key");
+ return hv;
+}
+
+
+/*** MapIterator *********************************************************************************/
+
+namespace {
+
+} /* anonymous namespace */
+
+static const ClassOps MapIteratorObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ MapIteratorObject::finalize
+};
+
+const Class MapIteratorObject::class_ = {
+ "Map Iterator",
+ JSCLASS_HAS_RESERVED_SLOTS(MapIteratorObject::SlotCount) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &MapIteratorObjectClassOps
+};
+
+const JSFunctionSpec MapIteratorObject::methods[] = {
+ JS_SELF_HOSTED_FN("next", "MapIteratorNext", 0, 0),
+ JS_FS_END
+};
+
+static inline ValueMap::Range*
+MapIteratorObjectRange(NativeObject* obj)
+{
+ MOZ_ASSERT(obj->is<MapIteratorObject>());
+ return static_cast<ValueMap::Range*>(obj->getSlot(MapIteratorObject::RangeSlot).toPrivate());
+}
+
+inline MapObject::IteratorKind
+MapIteratorObject::kind() const
+{
+ int32_t i = getSlot(KindSlot).toInt32();
+ MOZ_ASSERT(i == MapObject::Keys || i == MapObject::Values || i == MapObject::Entries);
+ return MapObject::IteratorKind(i);
+}
+
+bool
+GlobalObject::initMapIteratorProto(JSContext* cx, Handle<GlobalObject*> global)
+{
+ Rooted<JSObject*> base(cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
+ if (!base)
+ return false;
+ RootedPlainObject proto(cx, NewObjectWithGivenProto<PlainObject>(cx, base));
+ if (!proto)
+ return false;
+ if (!JS_DefineFunctions(cx, proto, MapIteratorObject::methods) ||
+ !DefineToStringTag(cx, proto, cx->names().MapIterator))
+ {
+ return false;
+ }
+ global->setReservedSlot(MAP_ITERATOR_PROTO, ObjectValue(*proto));
+ return true;
+}
+
+MapIteratorObject*
+MapIteratorObject::create(JSContext* cx, HandleObject mapobj, ValueMap* data,
+ MapObject::IteratorKind kind)
+{
+ Rooted<GlobalObject*> global(cx, &mapobj->global());
+ Rooted<JSObject*> proto(cx, GlobalObject::getOrCreateMapIteratorPrototype(cx, global));
+ if (!proto)
+ return nullptr;
+
+ ValueMap::Range* range = cx->new_<ValueMap::Range>(data->all());
+ if (!range)
+ return nullptr;
+
+ MapIteratorObject* iterobj = NewObjectWithGivenProto<MapIteratorObject>(cx, proto);
+ if (!iterobj) {
+ js_delete(range);
+ return nullptr;
+ }
+ iterobj->setSlot(TargetSlot, ObjectValue(*mapobj));
+ iterobj->setSlot(RangeSlot, PrivateValue(range));
+ iterobj->setSlot(KindSlot, Int32Value(int32_t(kind)));
+ return iterobj;
+}
+
+void
+MapIteratorObject::finalize(FreeOp* fop, JSObject* obj)
+{
+ MOZ_ASSERT(fop->onMainThread());
+ fop->delete_(MapIteratorObjectRange(static_cast<NativeObject*>(obj)));
+}
+
+bool
+MapIteratorObject::next(Handle<MapIteratorObject*> mapIterator, HandleArrayObject resultPairObj,
+ JSContext* cx)
+{
+ // Check invariants for inlined _GetNextMapEntryForIterator.
+
+ // The array should be tenured, so that post-barrier can be done simply.
+ MOZ_ASSERT(resultPairObj->isTenured());
+
+ // The array elements should be fixed.
+ MOZ_ASSERT(resultPairObj->hasFixedElements());
+ MOZ_ASSERT(resultPairObj->getDenseInitializedLength() == 2);
+ MOZ_ASSERT(resultPairObj->getDenseCapacity() >= 2);
+
+ ValueMap::Range* range = MapIteratorObjectRange(mapIterator);
+ if (!range || range->empty()) {
+ js_delete(range);
+ mapIterator->setReservedSlot(RangeSlot, PrivateValue(nullptr));
+ return true;
+ }
+ switch (mapIterator->kind()) {
+ case MapObject::Keys:
+ resultPairObj->setDenseElementWithType(cx, 0, range->front().key.get());
+ break;
+
+ case MapObject::Values:
+ resultPairObj->setDenseElementWithType(cx, 1, range->front().value);
+ break;
+
+ case MapObject::Entries: {
+ resultPairObj->setDenseElementWithType(cx, 0, range->front().key.get());
+ resultPairObj->setDenseElementWithType(cx, 1, range->front().value);
+ break;
+ }
+ }
+ range->popFront();
+ return false;
+}
+
+/* static */ JSObject*
+MapIteratorObject::createResultPair(JSContext* cx)
+{
+ RootedArrayObject resultPairObj(cx, NewDenseFullyAllocatedArray(cx, 2, nullptr, TenuredObject));
+ if (!resultPairObj)
+ return nullptr;
+
+ Rooted<TaggedProto> proto(cx, resultPairObj->taggedProto());
+ ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, resultPairObj->getClass(), proto);
+ if (!group)
+ return nullptr;
+ resultPairObj->setGroup(group);
+
+ resultPairObj->setDenseInitializedLength(2);
+ resultPairObj->initDenseElement(0, NullValue());
+ resultPairObj->initDenseElement(1, NullValue());
+
+ // See comments in MapIteratorObject::next.
+ AddTypePropertyId(cx, resultPairObj, JSID_VOID, TypeSet::UnknownType());
+
+ return resultPairObj;
+}
+
+
+/*** Map *****************************************************************************************/
+
+const ClassOps MapObject::classOps_ = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // getProperty
+ nullptr, // setProperty
+ nullptr, // enumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ finalize,
+ nullptr, // call
+ nullptr, // hasInstance
+ nullptr, // construct
+ mark
+};
+
+const Class MapObject::class_ = {
+ "Map",
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_HAS_RESERVED_SLOTS(MapObject::SlotCount) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Map) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &MapObject::classOps_
+};
+
+const JSPropertySpec MapObject::properties[] = {
+ JS_PSG("size", size, 0),
+ JS_PS_END
+};
+
+const JSFunctionSpec MapObject::methods[] = {
+ JS_FN("get", get, 1, 0),
+ JS_FN("has", has, 1, 0),
+ JS_FN("set", set, 2, 0),
+ JS_FN("delete", delete_, 1, 0),
+ JS_FN("keys", keys, 0, 0),
+ JS_FN("values", values, 0, 0),
+ JS_FN("clear", clear, 0, 0),
+ JS_SELF_HOSTED_FN("forEach", "MapForEach", 2, 0),
+ JS_FS_END
+};
+
+const JSPropertySpec MapObject::staticProperties[] = {
+ JS_SELF_HOSTED_SYM_GET(species, "MapSpecies", 0),
+ JS_PS_END
+};
+
+static JSObject*
+InitClass(JSContext* cx, Handle<GlobalObject*> global, const Class* clasp, JSProtoKey key, Native construct,
+ const JSPropertySpec* properties, const JSFunctionSpec* methods,
+ const JSPropertySpec* staticProperties)
+{
+ RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!proto)
+ return nullptr;
+
+ Rooted<JSFunction*> ctor(cx, global->createConstructor(cx, construct, ClassName(key, cx), 0));
+ if (!ctor ||
+ !JS_DefineProperties(cx, ctor, staticProperties) ||
+ !LinkConstructorAndPrototype(cx, ctor, proto) ||
+ !DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
+ !GlobalObject::initBuiltinConstructor(cx, global, key, ctor, proto))
+ {
+ return nullptr;
+ }
+ return proto;
+}
+
+JSObject*
+MapObject::initClass(JSContext* cx, JSObject* obj)
+{
+ Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+ RootedObject proto(cx,
+ InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods,
+ staticProperties));
+ if (proto) {
+ // Define the "entries" method.
+ JSFunction* fun = JS_DefineFunction(cx, proto, "entries", entries, 0, 0);
+ if (!fun)
+ return nullptr;
+
+ // Define its alias.
+ RootedValue funval(cx, ObjectValue(*fun));
+ RootedId iteratorId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator));
+ if (!JS_DefinePropertyById(cx, proto, iteratorId, funval, 0))
+ return nullptr;
+
+ // Define Map.prototype[@@toStringTag].
+ if (!DefineToStringTag(cx, proto, cx->names().Map))
+ return nullptr;
+ }
+ return proto;
+}
+
+template <class Range>
+static void
+MarkKey(Range& r, const HashableValue& key, JSTracer* trc)
+{
+ HashableValue newKey = key.mark(trc);
+
+ if (newKey.get() != key.get()) {
+ // The hash function only uses the bits of the Value, so it is safe to
+ // rekey even when the object or string has been modified by the GC.
+ r.rekeyFront(newKey);
+ }
+}
+
+void
+MapObject::mark(JSTracer* trc, JSObject* obj)
+{
+ if (ValueMap* map = obj->as<MapObject>().getData()) {
+ for (ValueMap::Range r = map->all(); !r.empty(); r.popFront()) {
+ MarkKey(r, r.front().key, trc);
+ TraceEdge(trc, &r.front().value, "value");
+ }
+ }
+}
+
+struct js::UnbarrieredHashPolicy {
+ typedef Value Lookup;
+ static HashNumber hash(const Lookup& v, const mozilla::HashCodeScrambler& hcs) {
+ return HashValue(v, hcs);
+ }
+ static bool match(const Value& k, const Lookup& l) { return k == l; }
+ static bool isEmpty(const Value& v) { return v.isMagic(JS_HASH_KEY_EMPTY); }
+ static void makeEmpty(Value* vp) { vp->setMagic(JS_HASH_KEY_EMPTY); }
+};
+
+using NurseryKeysVector = Vector<JSObject*, 0, SystemAllocPolicy>;
+
+template <typename TableObject>
+static NurseryKeysVector*
+GetNurseryKeys(TableObject* t)
+{
+ Value value = t->getReservedSlot(TableObject::NurseryKeysSlot);
+ return reinterpret_cast<NurseryKeysVector*>(value.toPrivate());
+}
+
+template <typename TableObject>
+static NurseryKeysVector*
+AllocNurseryKeys(TableObject* t)
+{
+ MOZ_ASSERT(!GetNurseryKeys(t));
+ auto keys = js_new<NurseryKeysVector>();
+ if (!keys)
+ return nullptr;
+
+ t->setReservedSlot(TableObject::NurseryKeysSlot, PrivateValue(keys));
+ return keys;
+}
+
+template <typename TableObject>
+static void
+DeleteNurseryKeys(TableObject* t)
+{
+ auto keys = GetNurseryKeys(t);
+ MOZ_ASSERT(keys);
+ js_delete(keys);
+ t->setReservedSlot(TableObject::NurseryKeysSlot, PrivateValue(nullptr));
+}
+
+// A generic store buffer entry that traces all nursery keys for an ordered hash
+// map or set.
+template <typename ObjectT>
+class js::OrderedHashTableRef : public gc::BufferableRef
+{
+ ObjectT* object;
+
+ public:
+ explicit OrderedHashTableRef(ObjectT* obj) : object(obj) {}
+
+ void trace(JSTracer* trc) override {
+ auto realTable = object->getData();
+ auto unbarrieredTable = reinterpret_cast<typename ObjectT::UnbarrieredTable*>(realTable);
+ NurseryKeysVector* keys = GetNurseryKeys(object);
+ MOZ_ASSERT(keys);
+ for (JSObject* obj : *keys) {
+ MOZ_ASSERT(obj);
+ Value key = ObjectValue(*obj);
+ Value prior = key;
+ MOZ_ASSERT(unbarrieredTable->hash(key) ==
+ realTable->hash(*reinterpret_cast<HashableValue*>(&key)));
+ TraceManuallyBarrieredEdge(trc, &key, "ordered hash table key");
+ unbarrieredTable->rekeyOneEntry(prior, key);
+ }
+ DeleteNurseryKeys(object);
+ }
+};
+
+template <typename ObjectT>
+inline static MOZ_MUST_USE bool
+WriteBarrierPostImpl(JSRuntime* rt, ObjectT* obj, const Value& keyValue)
+{
+ if (MOZ_LIKELY(!keyValue.isObject()))
+ return true;
+
+ JSObject* key = &keyValue.toObject();
+ if (!IsInsideNursery(key))
+ return true;
+
+ NurseryKeysVector* keys = GetNurseryKeys(obj);
+ if (!keys) {
+ keys = AllocNurseryKeys(obj);
+ if (!keys)
+ return false;
+
+ rt->gc.storeBuffer.putGeneric(OrderedHashTableRef<ObjectT>(obj));
+ }
+
+ if (!keys->append(key))
+ return false;
+
+ return true;
+}
+
+inline static MOZ_MUST_USE bool
+WriteBarrierPost(JSRuntime* rt, MapObject* map, const Value& key)
+{
+ return WriteBarrierPostImpl(rt, map, key);
+}
+
+inline static MOZ_MUST_USE bool
+WriteBarrierPost(JSRuntime* rt, SetObject* set, const Value& key)
+{
+ return WriteBarrierPostImpl(rt, set, key);
+}
+
+bool
+MapObject::getKeysAndValuesInterleaved(JSContext* cx, HandleObject obj,
+ JS::MutableHandle<GCVector<JS::Value>> entries)
+{
+ ValueMap* map = obj->as<MapObject>().getData();
+ if (!map)
+ return false;
+
+ for (ValueMap::Range r = map->all(); !r.empty(); r.popFront()) {
+ if (!entries.append(r.front().key.get()) ||
+ !entries.append(r.front().value))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+MapObject::set(JSContext* cx, HandleObject obj, HandleValue k, HandleValue v)
+{
+ ValueMap* map = obj->as<MapObject>().getData();
+ if (!map)
+ return false;
+
+ Rooted<HashableValue> key(cx);
+ if (!key.setValue(cx, k))
+ return false;
+
+ HeapPtr<Value> rval(v);
+ if (!WriteBarrierPost(cx->runtime(), &obj->as<MapObject>(), key.value()) ||
+ !map->put(key, rval))
+ {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ return true;
+}
+
+MapObject*
+MapObject::create(JSContext* cx, HandleObject proto /* = nullptr */)
+{
+ auto map = cx->make_unique<ValueMap>(cx->runtime(),
+ cx->compartment()->randomHashCodeScrambler());
+ if (!map || !map->init()) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ MapObject* mapObj = NewObjectWithClassProto<MapObject>(cx, proto);
+ if (!mapObj)
+ return nullptr;
+
+ mapObj->setPrivate(map.release());
+ mapObj->setReservedSlot(NurseryKeysSlot, PrivateValue(nullptr));
+ return mapObj;
+}
+
+void
+MapObject::finalize(FreeOp* fop, JSObject* obj)
+{
+ MOZ_ASSERT(fop->onMainThread());
+ if (ValueMap* map = obj->as<MapObject>().getData())
+ fop->delete_(map);
+}
+
+bool
+MapObject::construct(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "Map"))
+ return false;
+
+ RootedObject proto(cx);
+ RootedObject newTarget(cx, &args.newTarget().toObject());
+ if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+ return false;
+
+ Rooted<MapObject*> obj(cx, MapObject::create(cx, proto));
+ if (!obj)
+ return false;
+
+ if (!args.get(0).isNullOrUndefined()) {
+ FixedInvokeArgs<1> args2(cx);
+ args2[0].set(args[0]);
+
+ RootedValue thisv(cx, ObjectValue(*obj));
+ if (!CallSelfHostedFunction(cx, cx->names().MapConstructorInit, thisv, args2, args2.rval()))
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+bool
+MapObject::is(HandleValue v)
+{
+ return v.isObject() && v.toObject().hasClass(&class_) && v.toObject().as<MapObject>().getPrivate();
+}
+
+bool
+MapObject::is(HandleObject o)
+{
+ return o->hasClass(&class_) && o->as<MapObject>().getPrivate();
+}
+
+#define ARG0_KEY(cx, args, key) \
+ Rooted<HashableValue> key(cx); \
+ if (args.length() > 0 && !key.setValue(cx, args[0])) \
+ return false
+
+ValueMap&
+MapObject::extract(HandleObject o)
+{
+ MOZ_ASSERT(o->hasClass(&MapObject::class_));
+ return *o->as<MapObject>().getData();
+}
+
+ValueMap&
+MapObject::extract(const CallArgs& args)
+{
+ MOZ_ASSERT(args.thisv().isObject());
+ MOZ_ASSERT(args.thisv().toObject().hasClass(&MapObject::class_));
+ return *args.thisv().toObject().as<MapObject>().getData();
+}
+
+uint32_t
+MapObject::size(JSContext* cx, HandleObject obj)
+{
+ ValueMap& map = extract(obj);
+ static_assert(sizeof(map.count()) <= sizeof(uint32_t),
+ "map count must be precisely representable as a JS number");
+ return map.count();
+}
+
+bool
+MapObject::size_impl(JSContext* cx, const CallArgs& args)
+{
+ RootedObject obj(cx, &args.thisv().toObject());
+ args.rval().setNumber(size(cx, obj));
+ return true;
+}
+
+bool
+MapObject::size(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<MapObject::is, MapObject::size_impl>(cx, args);
+}
+
+bool
+MapObject::get(JSContext* cx, HandleObject obj,
+ HandleValue key, MutableHandleValue rval)
+{
+ ValueMap& map = extract(obj);
+ Rooted<HashableValue> k(cx);
+
+ if (!k.setValue(cx, key))
+ return false;
+
+ if (ValueMap::Entry* p = map.get(k))
+ rval.set(p->value);
+ else
+ rval.setUndefined();
+
+ return true;
+}
+
+bool
+MapObject::get_impl(JSContext* cx, const CallArgs& args)
+{
+ RootedObject obj(cx, &args.thisv().toObject());
+ return get(cx, obj, args.get(0), args.rval());
+}
+
+bool
+MapObject::get(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<MapObject::is, MapObject::get_impl>(cx, args);
+}
+
+bool
+MapObject::has(JSContext* cx, HandleObject obj, HandleValue key, bool* rval)
+{
+ ValueMap& map = extract(obj);
+ Rooted<HashableValue> k(cx);
+
+ if (!k.setValue(cx, key))
+ return false;
+
+ *rval = map.has(k);
+ return true;
+}
+
+bool
+MapObject::has_impl(JSContext* cx, const CallArgs& args)
+{
+ bool found;
+ RootedObject obj(cx, &args.thisv().toObject());
+ if (has(cx, obj, args.get(0), &found)) {
+ args.rval().setBoolean(found);
+ return true;
+ }
+ return false;
+}
+
+bool
+MapObject::has(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<MapObject::is, MapObject::has_impl>(cx, args);
+}
+
+bool
+MapObject::set_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(MapObject::is(args.thisv()));
+
+ ValueMap& map = extract(args);
+ ARG0_KEY(cx, args, key);
+ HeapPtr<Value> rval(args.get(1));
+ if (!WriteBarrierPost(cx->runtime(), &args.thisv().toObject().as<MapObject>(), key.value()) ||
+ !map.put(key, rval))
+ {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ args.rval().set(args.thisv());
+ return true;
+}
+
+bool
+MapObject::set(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<MapObject::is, MapObject::set_impl>(cx, args);
+}
+
+bool
+MapObject::delete_(JSContext *cx, HandleObject obj, HandleValue key, bool *rval)
+{
+ ValueMap &map = extract(obj);
+ Rooted<HashableValue> k(cx);
+
+ if (!k.setValue(cx, key))
+ return false;
+
+ if (!map.remove(k, rval)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ return true;
+}
+
+bool
+MapObject::delete_impl(JSContext *cx, const CallArgs& args)
+{
+ // MapObject::mark does not mark deleted entries. Incremental GC therefore
+ // requires that no HeapPtr<Value> objects pointing to heap values be left
+ // alive in the ValueMap.
+ //
+ // OrderedHashMap::remove() doesn't destroy the removed entry. It merely
+ // calls OrderedHashMap::MapOps::makeEmpty. But that is sufficient, because
+ // makeEmpty clears the value by doing e->value = Value(), and in the case
+ // of a ValueMap, Value() means HeapPtr<Value>(), which is the same as
+ // HeapPtr<Value>(UndefinedValue()).
+ MOZ_ASSERT(MapObject::is(args.thisv()));
+
+ ValueMap& map = extract(args);
+ ARG0_KEY(cx, args, key);
+ bool found;
+ if (!map.remove(key, &found)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ args.rval().setBoolean(found);
+ return true;
+}
+
+bool
+MapObject::delete_(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<MapObject::is, MapObject::delete_impl>(cx, args);
+}
+
+bool
+MapObject::iterator(JSContext* cx, IteratorKind kind,
+ HandleObject obj, MutableHandleValue iter)
+{
+ ValueMap& map = extract(obj);
+ Rooted<JSObject*> iterobj(cx, MapIteratorObject::create(cx, obj, &map, kind));
+ return iterobj && (iter.setObject(*iterobj), true);
+}
+
+bool
+MapObject::iterator_impl(JSContext* cx, const CallArgs& args, IteratorKind kind)
+{
+ RootedObject obj(cx, &args.thisv().toObject());
+ return iterator(cx, kind, obj, args.rval());
+}
+
+bool
+MapObject::keys_impl(JSContext* cx, const CallArgs& args)
+{
+ return iterator_impl(cx, args, Keys);
+}
+
+bool
+MapObject::keys(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod(cx, is, keys_impl, args);
+}
+
+bool
+MapObject::values_impl(JSContext* cx, const CallArgs& args)
+{
+ return iterator_impl(cx, args, Values);
+}
+
+bool
+MapObject::values(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod(cx, is, values_impl, args);
+}
+
+bool
+MapObject::entries_impl(JSContext* cx, const CallArgs& args)
+{
+ return iterator_impl(cx, args, Entries);
+}
+
+bool
+MapObject::entries(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod(cx, is, entries_impl, args);
+}
+
+bool
+MapObject::clear_impl(JSContext* cx, const CallArgs& args)
+{
+ RootedObject obj(cx, &args.thisv().toObject());
+ args.rval().setUndefined();
+ return clear(cx, obj);
+}
+
+bool
+MapObject::clear(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod(cx, is, clear_impl, args);
+}
+
+bool
+MapObject::clear(JSContext* cx, HandleObject obj)
+{
+ ValueMap& map = extract(obj);
+ if (!map.clear()) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ return true;
+}
+
+JSObject*
+js::InitMapClass(JSContext* cx, HandleObject obj)
+{
+ return MapObject::initClass(cx, obj);
+}
+
+
+/*** SetIterator *********************************************************************************/
+
+static const ClassOps SetIteratorObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ SetIteratorObject::finalize
+};
+
+const Class SetIteratorObject::class_ = {
+ "Set Iterator",
+ JSCLASS_HAS_RESERVED_SLOTS(SetIteratorObject::SlotCount) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &SetIteratorObjectClassOps
+};
+
+const JSFunctionSpec SetIteratorObject::methods[] = {
+ JS_SELF_HOSTED_FN("next", "SetIteratorNext", 0, 0),
+ JS_FS_END
+};
+
+static inline ValueSet::Range*
+SetIteratorObjectRange(NativeObject* obj)
+{
+ MOZ_ASSERT(obj->is<SetIteratorObject>());
+ return static_cast<ValueSet::Range*>(obj->getSlot(SetIteratorObject::RangeSlot).toPrivate());
+}
+
+inline SetObject::IteratorKind
+SetIteratorObject::kind() const
+{
+ int32_t i = getSlot(KindSlot).toInt32();
+ MOZ_ASSERT(i == SetObject::Values || i == SetObject::Entries);
+ return SetObject::IteratorKind(i);
+}
+
+bool
+GlobalObject::initSetIteratorProto(JSContext* cx, Handle<GlobalObject*> global)
+{
+ Rooted<JSObject*> base(cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
+ if (!base)
+ return false;
+ RootedPlainObject proto(cx, NewObjectWithGivenProto<PlainObject>(cx, base));
+ if (!proto)
+ return false;
+ if (!JS_DefineFunctions(cx, proto, SetIteratorObject::methods) ||
+ !DefineToStringTag(cx, proto, cx->names().SetIterator))
+ {
+ return false;
+ }
+ global->setReservedSlot(SET_ITERATOR_PROTO, ObjectValue(*proto));
+ return true;
+}
+
+SetIteratorObject*
+SetIteratorObject::create(JSContext* cx, HandleObject setobj, ValueSet* data,
+ SetObject::IteratorKind kind)
+{
+ MOZ_ASSERT(kind != SetObject::Keys);
+
+ Rooted<GlobalObject*> global(cx, &setobj->global());
+ Rooted<JSObject*> proto(cx, GlobalObject::getOrCreateSetIteratorPrototype(cx, global));
+ if (!proto)
+ return nullptr;
+
+ ValueSet::Range* range = cx->new_<ValueSet::Range>(data->all());
+ if (!range)
+ return nullptr;
+
+ SetIteratorObject* iterobj = NewObjectWithGivenProto<SetIteratorObject>(cx, proto);
+ if (!iterobj) {
+ js_delete(range);
+ return nullptr;
+ }
+ iterobj->setSlot(TargetSlot, ObjectValue(*setobj));
+ iterobj->setSlot(RangeSlot, PrivateValue(range));
+ iterobj->setSlot(KindSlot, Int32Value(int32_t(kind)));
+ return iterobj;
+}
+
+void
+SetIteratorObject::finalize(FreeOp* fop, JSObject* obj)
+{
+ MOZ_ASSERT(fop->onMainThread());
+ fop->delete_(SetIteratorObjectRange(static_cast<NativeObject*>(obj)));
+}
+
+bool
+SetIteratorObject::next(Handle<SetIteratorObject*> setIterator, HandleArrayObject resultObj,
+ JSContext* cx)
+{
+ // Check invariants for inlined _GetNextSetEntryForIterator.
+
+ // The array should be tenured, so that post-barrier can be done simply.
+ MOZ_ASSERT(resultObj->isTenured());
+
+ // The array elements should be fixed.
+ MOZ_ASSERT(resultObj->hasFixedElements());
+ MOZ_ASSERT(resultObj->getDenseInitializedLength() == 1);
+ MOZ_ASSERT(resultObj->getDenseCapacity() >= 1);
+
+ ValueSet::Range* range = SetIteratorObjectRange(setIterator);
+ if (!range || range->empty()) {
+ js_delete(range);
+ setIterator->setReservedSlot(RangeSlot, PrivateValue(nullptr));
+ return true;
+ }
+ resultObj->setDenseElementWithType(cx, 0, range->front().get());
+ range->popFront();
+ return false;
+}
+
+/* static */ JSObject*
+SetIteratorObject::createResult(JSContext* cx)
+{
+ RootedArrayObject resultObj(cx, NewDenseFullyAllocatedArray(cx, 1, nullptr, TenuredObject));
+ if (!resultObj)
+ return nullptr;
+
+ Rooted<TaggedProto> proto(cx, resultObj->taggedProto());
+ ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, resultObj->getClass(), proto);
+ if (!group)
+ return nullptr;
+ resultObj->setGroup(group);
+
+ resultObj->setDenseInitializedLength(1);
+ resultObj->initDenseElement(0, NullValue());
+
+ // See comments in SetIteratorObject::next.
+ AddTypePropertyId(cx, resultObj, JSID_VOID, TypeSet::UnknownType());
+
+ return resultObj;
+}
+
+
+/*** Set *****************************************************************************************/
+
+const ClassOps SetObject::classOps_ = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // getProperty
+ nullptr, // setProperty
+ nullptr, // enumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ finalize,
+ nullptr, // call
+ nullptr, // hasInstance
+ nullptr, // construct
+ mark
+};
+
+const Class SetObject::class_ = {
+ "Set",
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_HAS_RESERVED_SLOTS(SetObject::SlotCount) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Set) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &SetObject::classOps_
+};
+
+const JSPropertySpec SetObject::properties[] = {
+ JS_PSG("size", size, 0),
+ JS_PS_END
+};
+
+const JSFunctionSpec SetObject::methods[] = {
+ JS_FN("has", has, 1, 0),
+ JS_FN("add", add, 1, 0),
+ JS_FN("delete", delete_, 1, 0),
+ JS_FN("entries", entries, 0, 0),
+ JS_FN("clear", clear, 0, 0),
+ JS_SELF_HOSTED_FN("forEach", "SetForEach", 2, 0),
+ JS_FS_END
+};
+
+const JSPropertySpec SetObject::staticProperties[] = {
+ JS_SELF_HOSTED_SYM_GET(species, "SetSpecies", 0),
+ JS_PS_END
+};
+
+JSObject*
+SetObject::initClass(JSContext* cx, JSObject* obj)
+{
+ Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+ RootedObject proto(cx,
+ InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods,
+ staticProperties));
+ if (proto) {
+ // Define the "values" method.
+ JSFunction* fun = JS_DefineFunction(cx, proto, "values", values, 0, 0);
+ if (!fun)
+ return nullptr;
+
+ // Define its aliases.
+ RootedValue funval(cx, ObjectValue(*fun));
+ if (!JS_DefineProperty(cx, proto, "keys", funval, 0))
+ return nullptr;
+
+ RootedId iteratorId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator));
+ if (!JS_DefinePropertyById(cx, proto, iteratorId, funval, 0))
+ return nullptr;
+
+ // Define Set.prototype[@@toStringTag].
+ if (!DefineToStringTag(cx, proto, cx->names().Set))
+ return nullptr;
+ }
+ return proto;
+}
+
+
+bool
+SetObject::keys(JSContext* cx, HandleObject obj, JS::MutableHandle<GCVector<JS::Value>> keys)
+{
+ ValueSet* set = obj->as<SetObject>().getData();
+ if (!set)
+ return false;
+
+ for (ValueSet::Range r = set->all(); !r.empty(); r.popFront()) {
+ if (!keys.append(r.front().get()))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+SetObject::add(JSContext* cx, HandleObject obj, HandleValue k)
+{
+ ValueSet* set = obj->as<SetObject>().getData();
+ if (!set)
+ return false;
+
+ Rooted<HashableValue> key(cx);
+ if (!key.setValue(cx, k))
+ return false;
+
+ if (!WriteBarrierPost(cx->runtime(), &obj->as<SetObject>(), key.value()) ||
+ !set->put(key))
+ {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ return true;
+}
+
+SetObject*
+SetObject::create(JSContext* cx, HandleObject proto /* = nullptr */)
+{
+ auto set = cx->make_unique<ValueSet>(cx->runtime(),
+ cx->compartment()->randomHashCodeScrambler());
+ if (!set || !set->init()) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ SetObject* obj = NewObjectWithClassProto<SetObject>(cx, proto);
+ if (!obj)
+ return nullptr;
+
+ obj->setPrivate(set.release());
+ obj->setReservedSlot(NurseryKeysSlot, PrivateValue(nullptr));
+ return obj;
+}
+
+void
+SetObject::mark(JSTracer* trc, JSObject* obj)
+{
+ SetObject* setobj = static_cast<SetObject*>(obj);
+ if (ValueSet* set = setobj->getData()) {
+ for (ValueSet::Range r = set->all(); !r.empty(); r.popFront())
+ MarkKey(r, r.front(), trc);
+ }
+}
+
+void
+SetObject::finalize(FreeOp* fop, JSObject* obj)
+{
+ MOZ_ASSERT(fop->onMainThread());
+ SetObject* setobj = static_cast<SetObject*>(obj);
+ if (ValueSet* set = setobj->getData())
+ fop->delete_(set);
+}
+
+bool
+SetObject::isBuiltinAdd(HandleValue add, JSContext* cx)
+{
+ return IsNativeFunction(add, SetObject::add);
+}
+
+bool
+SetObject::construct(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "Set"))
+ return false;
+
+ RootedObject proto(cx);
+ RootedObject newTarget(cx, &args.newTarget().toObject());
+ if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+ return false;
+
+ Rooted<SetObject*> obj(cx, SetObject::create(cx, proto));
+ if (!obj)
+ return false;
+
+ if (!args.get(0).isNullOrUndefined()) {
+ RootedValue iterable(cx, args[0]);
+ bool optimized = false;
+ if (!IsOptimizableInitForSet<GlobalObject::getOrCreateSetPrototype, isBuiltinAdd>(cx, obj, iterable, &optimized))
+ return false;
+
+ if (optimized) {
+ RootedValue keyVal(cx);
+ Rooted<HashableValue> key(cx);
+ ValueSet* set = obj->getData();
+ ArrayObject* array = &iterable.toObject().as<ArrayObject>();
+ for (uint32_t index = 0; index < array->getDenseInitializedLength(); ++index) {
+ keyVal.set(array->getDenseElement(index));
+ MOZ_ASSERT(!keyVal.isMagic(JS_ELEMENTS_HOLE));
+
+ if (!key.setValue(cx, keyVal))
+ return false;
+ if (!WriteBarrierPost(cx->runtime(), obj, keyVal) ||
+ !set->put(key))
+ {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+ } else {
+ FixedInvokeArgs<1> args2(cx);
+ args2[0].set(args[0]);
+
+ RootedValue thisv(cx, ObjectValue(*obj));
+ if (!CallSelfHostedFunction(cx, cx->names().SetConstructorInit, thisv, args2, args2.rval()))
+ return false;
+ }
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+bool
+SetObject::is(HandleValue v)
+{
+ return v.isObject() && v.toObject().hasClass(&class_) && v.toObject().as<SetObject>().getPrivate();
+}
+
+bool
+SetObject::is(HandleObject o)
+{
+ return o->hasClass(&class_) && o->as<SetObject>().getPrivate();
+}
+
+ValueSet &
+SetObject::extract(HandleObject o)
+{
+ MOZ_ASSERT(o->hasClass(&SetObject::class_));
+ return *o->as<SetObject>().getData();
+}
+
+ValueSet &
+SetObject::extract(const CallArgs& args)
+{
+ MOZ_ASSERT(args.thisv().isObject());
+ MOZ_ASSERT(args.thisv().toObject().hasClass(&SetObject::class_));
+ return *static_cast<SetObject&>(args.thisv().toObject()).getData();
+}
+
+uint32_t
+SetObject::size(JSContext *cx, HandleObject obj)
+{
+ MOZ_ASSERT(SetObject::is(obj));
+ ValueSet &set = extract(obj);
+ static_assert(sizeof(set.count()) <= sizeof(uint32_t),
+ "set count must be precisely representable as a JS number");
+ return set.count();
+}
+
+bool
+SetObject::size_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(is(args.thisv()));
+
+ ValueSet& set = extract(args);
+ static_assert(sizeof(set.count()) <= sizeof(uint32_t),
+ "set count must be precisely representable as a JS number");
+ args.rval().setNumber(set.count());
+ return true;
+}
+
+bool
+SetObject::size(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<SetObject::is, SetObject::size_impl>(cx, args);
+}
+
+bool
+SetObject::has_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(is(args.thisv()));
+
+ ValueSet& set = extract(args);
+ ARG0_KEY(cx, args, key);
+ args.rval().setBoolean(set.has(key));
+ return true;
+}
+
+bool
+SetObject::has(JSContext *cx, HandleObject obj, HandleValue key, bool *rval)
+{
+ MOZ_ASSERT(SetObject::is(obj));
+
+ ValueSet &set = extract(obj);
+ Rooted<HashableValue> k(cx);
+
+ if (!k.setValue(cx, key))
+ return false;
+
+ *rval = set.has(k);
+ return true;
+}
+
+bool
+SetObject::has(JSContext *cx, unsigned argc, Value *vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<SetObject::is, SetObject::has_impl>(cx, args);
+}
+
+bool
+SetObject::add_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(is(args.thisv()));
+
+ ValueSet& set = extract(args);
+ ARG0_KEY(cx, args, key);
+ if (!WriteBarrierPost(cx->runtime(), &args.thisv().toObject().as<SetObject>(), key.value()) ||
+ !set.put(key))
+ {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ args.rval().set(args.thisv());
+ return true;
+}
+
+bool
+SetObject::add(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<SetObject::is, SetObject::add_impl>(cx, args);
+}
+
+bool
+SetObject::delete_(JSContext *cx, HandleObject obj, HandleValue key, bool *rval)
+{
+ MOZ_ASSERT(SetObject::is(obj));
+
+ ValueSet &set = extract(obj);
+ Rooted<HashableValue> k(cx);
+
+ if (!k.setValue(cx, key))
+ return false;
+
+ if (!set.remove(k, rval)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ return true;
+}
+
+bool
+SetObject::delete_impl(JSContext *cx, const CallArgs& args)
+{
+ MOZ_ASSERT(is(args.thisv()));
+
+ ValueSet& set = extract(args);
+ ARG0_KEY(cx, args, key);
+ bool found;
+ if (!set.remove(key, &found)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ args.rval().setBoolean(found);
+ return true;
+}
+
+bool
+SetObject::delete_(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<SetObject::is, SetObject::delete_impl>(cx, args);
+}
+
+bool
+SetObject::iterator(JSContext *cx, IteratorKind kind,
+ HandleObject obj, MutableHandleValue iter)
+{
+ MOZ_ASSERT(SetObject::is(obj));
+ ValueSet &set = extract(obj);
+ Rooted<JSObject*> iterobj(cx, SetIteratorObject::create(cx, obj, &set, kind));
+ return iterobj && (iter.setObject(*iterobj), true);
+}
+
+bool
+SetObject::iterator_impl(JSContext *cx, const CallArgs& args, IteratorKind kind)
+{
+ Rooted<SetObject*> setobj(cx, &args.thisv().toObject().as<SetObject>());
+ ValueSet& set = *setobj->getData();
+ Rooted<JSObject*> iterobj(cx, SetIteratorObject::create(cx, setobj, &set, kind));
+ if (!iterobj)
+ return false;
+ args.rval().setObject(*iterobj);
+ return true;
+}
+
+bool
+SetObject::values_impl(JSContext* cx, const CallArgs& args)
+{
+ return iterator_impl(cx, args, Values);
+}
+
+bool
+SetObject::values(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod(cx, is, values_impl, args);
+}
+
+bool
+SetObject::entries_impl(JSContext* cx, const CallArgs& args)
+{
+ return iterator_impl(cx, args, Entries);
+}
+
+bool
+SetObject::entries(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod(cx, is, entries_impl, args);
+}
+
+bool
+SetObject::clear(JSContext *cx, HandleObject obj)
+{
+ MOZ_ASSERT(SetObject::is(obj));
+ ValueSet &set = extract(obj);
+ if (!set.clear()) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ return true;
+}
+
+bool
+SetObject::clear_impl(JSContext *cx, const CallArgs& args)
+{
+ Rooted<SetObject*> setobj(cx, &args.thisv().toObject().as<SetObject>());
+ if (!setobj->getData()->clear()) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ args.rval().setUndefined();
+ return true;
+}
+
+bool
+SetObject::clear(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod(cx, is, clear_impl, args);
+}
+
+JSObject*
+js::InitSetClass(JSContext* cx, HandleObject obj)
+{
+ return SetObject::initClass(cx, obj);
+}
+
+/*** JS static utility functions *********************************************/
+
+static bool
+forEach(const char* funcName, JSContext *cx, HandleObject obj, HandleValue callbackFn, HandleValue thisArg)
+{
+ CHECK_REQUEST(cx);
+
+ RootedId forEachId(cx, NameToId(cx->names().forEach));
+ RootedFunction forEachFunc(cx, JS::GetSelfHostedFunction(cx, funcName, forEachId, 2));
+ if (!forEachFunc)
+ return false;
+
+ RootedValue fval(cx, ObjectValue(*forEachFunc));
+ return Call(cx, fval, obj, callbackFn, thisArg, &fval);
+}
+
+// Handles Clear/Size for public jsapi map/set access
+template<typename RetT>
+RetT
+CallObjFunc(RetT(*ObjFunc)(JSContext*, HandleObject), JSContext* cx, HandleObject obj)
+{
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, obj);
+
+ // Always unwrap, in case this is an xray or cross-compartment wrapper.
+ RootedObject unwrappedObj(cx);
+ unwrappedObj = UncheckedUnwrap(obj);
+
+ // Enter the compartment of the backing object before calling functions on
+ // it.
+ JSAutoCompartment ac(cx, unwrappedObj);
+ return ObjFunc(cx, unwrappedObj);
+}
+
+// Handles Has/Delete for public jsapi map/set access
+bool
+CallObjFunc(bool(*ObjFunc)(JSContext *cx, HandleObject obj, HandleValue key, bool *rval),
+ JSContext *cx, HandleObject obj, HandleValue key, bool *rval)
+{
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, obj, key);
+
+ // Always unwrap, in case this is an xray or cross-compartment wrapper.
+ RootedObject unwrappedObj(cx);
+ unwrappedObj = UncheckedUnwrap(obj);
+ JSAutoCompartment ac(cx, unwrappedObj);
+
+ // If we're working with a wrapped map/set, rewrap the key into the
+ // compartment of the unwrapped map/set.
+ RootedValue wrappedKey(cx, key);
+ if (obj != unwrappedObj) {
+ if (!JS_WrapValue(cx, &wrappedKey))
+ return false;
+ }
+ return ObjFunc(cx, unwrappedObj, wrappedKey, rval);
+}
+
+// Handles iterator generation for public jsapi map/set access
+template<typename Iter>
+bool
+CallObjFunc(bool(*ObjFunc)(JSContext* cx, Iter kind,
+ HandleObject obj, MutableHandleValue iter),
+ JSContext *cx, Iter iterType, HandleObject obj, MutableHandleValue rval)
+{
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, obj);
+
+ // Always unwrap, in case this is an xray or cross-compartment wrapper.
+ RootedObject unwrappedObj(cx);
+ unwrappedObj = UncheckedUnwrap(obj);
+ {
+ // Retrieve the iterator while in the unwrapped map/set's compartment,
+ // otherwise we'll crash on a compartment assert.
+ JSAutoCompartment ac(cx, unwrappedObj);
+ if (!ObjFunc(cx, iterType, unwrappedObj, rval))
+ return false;
+ }
+
+ // If the caller is in a different compartment than the map/set, rewrap the
+ // iterator object into the caller's compartment.
+ if (obj != unwrappedObj) {
+ if (!JS_WrapValue(cx, rval))
+ return false;
+ }
+ return true;
+}
+
+/*** JS public APIs **********************************************************/
+
+JS_PUBLIC_API(JSObject*)
+JS::NewMapObject(JSContext* cx)
+{
+ return MapObject::create(cx);
+}
+
+JS_PUBLIC_API(uint32_t)
+JS::MapSize(JSContext* cx, HandleObject obj)
+{
+ return CallObjFunc<uint32_t>(&MapObject::size, cx, obj);
+}
+
+JS_PUBLIC_API(bool)
+JS::MapGet(JSContext* cx, HandleObject obj, HandleValue key, MutableHandleValue rval)
+{
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, obj, key, rval);
+
+ // Unwrap the object, and enter its compartment. If object isn't wrapped,
+ // this is essentially a noop.
+ RootedObject unwrappedObj(cx);
+ unwrappedObj = UncheckedUnwrap(obj);
+ {
+ JSAutoCompartment ac(cx, unwrappedObj);
+ RootedValue wrappedKey(cx, key);
+
+ // If we passed in a wrapper, wrap our key into its compartment now.
+ if (obj != unwrappedObj) {
+ if (!JS_WrapValue(cx, &wrappedKey))
+ return false;
+ }
+ if (!MapObject::get(cx, unwrappedObj, wrappedKey, rval))
+ return false;
+ }
+
+ // If we passed in a wrapper, wrap our return value on the way out.
+ if (obj != unwrappedObj) {
+ if (!JS_WrapValue(cx, rval))
+ return false;
+ }
+ return true;
+}
+
+JS_PUBLIC_API(bool)
+JS::MapSet(JSContext *cx, HandleObject obj, HandleValue key, HandleValue val)
+{
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, obj, key, val);
+
+ // Unwrap the object, and enter its compartment. If object isn't wrapped,
+ // this is essentially a noop.
+ RootedObject unwrappedObj(cx);
+ unwrappedObj = UncheckedUnwrap(obj);
+ {
+ JSAutoCompartment ac(cx, unwrappedObj);
+
+ // If we passed in a wrapper, wrap both key and value before adding to
+ // the map
+ RootedValue wrappedKey(cx, key);
+ RootedValue wrappedValue(cx, val);
+ if (obj != unwrappedObj) {
+ if (!JS_WrapValue(cx, &wrappedKey) ||
+ !JS_WrapValue(cx, &wrappedValue)) {
+ return false;
+ }
+ }
+ return MapObject::set(cx, unwrappedObj, wrappedKey, wrappedValue);
+ }
+}
+
+JS_PUBLIC_API(bool)
+JS::MapHas(JSContext* cx, HandleObject obj, HandleValue key, bool* rval)
+{
+ return CallObjFunc(MapObject::has, cx, obj, key, rval);
+}
+
+JS_PUBLIC_API(bool)
+JS::MapDelete(JSContext *cx, HandleObject obj, HandleValue key, bool* rval)
+{
+ return CallObjFunc(MapObject::delete_, cx, obj, key, rval);
+}
+
+JS_PUBLIC_API(bool)
+JS::MapClear(JSContext* cx, HandleObject obj)
+{
+ return CallObjFunc(&MapObject::clear, cx, obj);
+}
+
+JS_PUBLIC_API(bool)
+JS::MapKeys(JSContext* cx, HandleObject obj, MutableHandleValue rval)
+{
+ return CallObjFunc(&MapObject::iterator, cx, MapObject::Keys, obj, rval);
+}
+
+JS_PUBLIC_API(bool)
+JS::MapValues(JSContext* cx, HandleObject obj, MutableHandleValue rval)
+{
+ return CallObjFunc(&MapObject::iterator, cx, MapObject::Values, obj, rval);
+}
+
+JS_PUBLIC_API(bool)
+JS::MapEntries(JSContext* cx, HandleObject obj, MutableHandleValue rval)
+{
+ return CallObjFunc(&MapObject::iterator, cx, MapObject::Entries, obj, rval);
+}
+
+JS_PUBLIC_API(bool)
+JS::MapForEach(JSContext *cx, HandleObject obj, HandleValue callbackFn, HandleValue thisVal)
+{
+ return forEach("MapForEach", cx, obj, callbackFn, thisVal);
+}
+
+JS_PUBLIC_API(JSObject *)
+JS::NewSetObject(JSContext *cx)
+{
+ return SetObject::create(cx);
+}
+
+JS_PUBLIC_API(uint32_t)
+JS::SetSize(JSContext *cx, HandleObject obj)
+{
+ return CallObjFunc<uint32_t>(&SetObject::size, cx, obj);
+}
+
+JS_PUBLIC_API(bool)
+JS::SetAdd(JSContext *cx, HandleObject obj, HandleValue key)
+{
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, obj, key);
+
+ // Unwrap the object, and enter its compartment. If object isn't wrapped,
+ // this is essentially a noop.
+ RootedObject unwrappedObj(cx);
+ unwrappedObj = UncheckedUnwrap(obj);
+ {
+ JSAutoCompartment ac(cx, unwrappedObj);
+
+ // If we passed in a wrapper, wrap key before adding to the set
+ RootedValue wrappedKey(cx, key);
+ if (obj != unwrappedObj) {
+ if (!JS_WrapValue(cx, &wrappedKey))
+ return false;
+ }
+ return SetObject::add(cx, unwrappedObj, wrappedKey);
+ }
+}
+
+JS_PUBLIC_API(bool)
+JS::SetHas(JSContext* cx, HandleObject obj, HandleValue key, bool* rval)
+{
+ return CallObjFunc(SetObject::has, cx, obj, key, rval);
+}
+
+JS_PUBLIC_API(bool)
+JS::SetDelete(JSContext *cx, HandleObject obj, HandleValue key, bool *rval)
+{
+ return CallObjFunc(SetObject::delete_, cx, obj, key, rval);
+}
+
+JS_PUBLIC_API(bool)
+JS::SetClear(JSContext* cx, HandleObject obj)
+{
+ return CallObjFunc(&SetObject::clear, cx, obj);
+}
+
+JS_PUBLIC_API(bool)
+JS::SetKeys(JSContext* cx, HandleObject obj, MutableHandleValue rval)
+{
+ return SetValues(cx, obj, rval);
+}
+
+JS_PUBLIC_API(bool)
+JS::SetValues(JSContext* cx, HandleObject obj, MutableHandleValue rval)
+{
+ return CallObjFunc(&SetObject::iterator, cx, SetObject::Values, obj, rval);
+}
+
+JS_PUBLIC_API(bool)
+JS::SetEntries(JSContext* cx, HandleObject obj, MutableHandleValue rval)
+{
+ return CallObjFunc(&SetObject::iterator, cx, SetObject::Entries, obj, rval);
+}
+
+JS_PUBLIC_API(bool)
+JS::SetForEach(JSContext *cx, HandleObject obj, HandleValue callbackFn, HandleValue thisVal)
+{
+ return forEach("SetForEach", cx, obj, callbackFn, thisVal);
+}
diff --git a/js/src/builtin/MapObject.h b/js/src/builtin/MapObject.h
new file mode 100644
index 000000000..9473e6b70
--- /dev/null
+++ b/js/src/builtin/MapObject.h
@@ -0,0 +1,341 @@
+/* -*- 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/. */
+
+#ifndef builtin_MapObject_h
+#define builtin_MapObject_h
+
+#include "jsobj.h"
+
+#include "builtin/SelfHostingDefines.h"
+#include "vm/GlobalObject.h"
+#include "vm/NativeObject.h"
+#include "vm/PIC.h"
+#include "vm/Runtime.h"
+
+namespace js {
+
+/*
+ * Comparing two ropes for equality can fail. The js::HashTable template
+ * requires infallible hash() and match() operations. Therefore we require
+ * all values to be converted to hashable form before being used as a key
+ * in a Map or Set object.
+ *
+ * All values except ropes are hashable as-is.
+ */
+class HashableValue
+{
+ PreBarrieredValue value;
+
+ public:
+ struct Hasher {
+ typedef HashableValue Lookup;
+ static HashNumber hash(const Lookup& v, const mozilla::HashCodeScrambler& hcs) {
+ return v.hash(hcs);
+ }
+ static bool match(const HashableValue& k, const Lookup& l) { return k == l; }
+ static bool isEmpty(const HashableValue& v) { return v.value.isMagic(JS_HASH_KEY_EMPTY); }
+ static void makeEmpty(HashableValue* vp) { vp->value = MagicValue(JS_HASH_KEY_EMPTY); }
+ };
+
+ HashableValue() : value(UndefinedValue()) {}
+
+ MOZ_MUST_USE bool setValue(JSContext* cx, HandleValue v);
+ HashNumber hash(const mozilla::HashCodeScrambler& hcs) const;
+ bool operator==(const HashableValue& other) const;
+ HashableValue mark(JSTracer* trc) const;
+ Value get() const { return value.get(); }
+
+ void trace(JSTracer* trc) {
+ TraceEdge(trc, &value, "HashableValue");
+ }
+};
+
+template <>
+class RootedBase<HashableValue> {
+ public:
+ MOZ_MUST_USE bool setValue(JSContext* cx, HandleValue v) {
+ return static_cast<JS::Rooted<HashableValue>*>(this)->get().setValue(cx, v);
+ }
+ Value value() const {
+ return static_cast<const JS::Rooted<HashableValue>*>(this)->get().get();
+ }
+};
+
+template <class Key, class Value, class OrderedHashPolicy, class AllocPolicy>
+class OrderedHashMap;
+
+template <class T, class OrderedHashPolicy, class AllocPolicy>
+class OrderedHashSet;
+
+typedef OrderedHashMap<HashableValue,
+ HeapPtr<Value>,
+ HashableValue::Hasher,
+ RuntimeAllocPolicy> ValueMap;
+
+typedef OrderedHashSet<HashableValue,
+ HashableValue::Hasher,
+ RuntimeAllocPolicy> ValueSet;
+
+template <typename ObjectT>
+class OrderedHashTableRef;
+
+struct UnbarrieredHashPolicy;
+
+class MapObject : public NativeObject {
+ public:
+ enum IteratorKind { Keys, Values, Entries };
+ static_assert(Keys == ITEM_KIND_KEY,
+ "IteratorKind Keys must match self-hosting define for item kind key.");
+ static_assert(Values == ITEM_KIND_VALUE,
+ "IteratorKind Values must match self-hosting define for item kind value.");
+ static_assert(Entries == ITEM_KIND_KEY_AND_VALUE,
+ "IteratorKind Entries must match self-hosting define for item kind "
+ "key-and-value.");
+
+ static JSObject* initClass(JSContext* cx, JSObject* obj);
+ static const Class class_;
+
+ enum { NurseryKeysSlot, SlotCount };
+
+ static MOZ_MUST_USE bool getKeysAndValuesInterleaved(JSContext* cx, HandleObject obj,
+ JS::MutableHandle<GCVector<JS::Value>> entries);
+ static MOZ_MUST_USE bool entries(JSContext* cx, unsigned argc, Value* vp);
+ static MOZ_MUST_USE bool has(JSContext* cx, unsigned argc, Value* vp);
+ static MapObject* create(JSContext* cx, HandleObject proto = nullptr);
+
+ // Publicly exposed Map calls for JSAPI access (webidl maplike/setlike
+ // interfaces, etc.)
+ static uint32_t size(JSContext *cx, HandleObject obj);
+ static MOZ_MUST_USE bool get(JSContext *cx, HandleObject obj, HandleValue key,
+ MutableHandleValue rval);
+ static MOZ_MUST_USE bool has(JSContext *cx, HandleObject obj, HandleValue key, bool* rval);
+ static MOZ_MUST_USE bool delete_(JSContext *cx, HandleObject obj, HandleValue key, bool* rval);
+
+ // Set call for public JSAPI exposure. Does not actually return map object
+ // as stated in spec, expects caller to return a value. for instance, with
+ // webidl maplike/setlike, should return interface object.
+ static MOZ_MUST_USE bool set(JSContext *cx, HandleObject obj, HandleValue key, HandleValue val);
+ static MOZ_MUST_USE bool clear(JSContext *cx, HandleObject obj);
+ static MOZ_MUST_USE bool iterator(JSContext *cx, IteratorKind kind, HandleObject obj,
+ MutableHandleValue iter);
+
+ using UnbarrieredTable = OrderedHashMap<Value, Value, UnbarrieredHashPolicy, RuntimeAllocPolicy>;
+ friend class OrderedHashTableRef<MapObject>;
+
+ private:
+ static const ClassOps classOps_;
+
+ static const JSPropertySpec properties[];
+ static const JSFunctionSpec methods[];
+ static const JSPropertySpec staticProperties[];
+ ValueMap* getData() { return static_cast<ValueMap*>(getPrivate()); }
+ static ValueMap& extract(HandleObject o);
+ static ValueMap& extract(const CallArgs& args);
+ static void mark(JSTracer* trc, JSObject* obj);
+ static void finalize(FreeOp* fop, JSObject* obj);
+ static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
+
+ static bool is(HandleValue v);
+ static bool is(HandleObject o);
+
+ static MOZ_MUST_USE bool iterator_impl(JSContext* cx, const CallArgs& args, IteratorKind kind);
+
+ static MOZ_MUST_USE bool size_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool size(JSContext* cx, unsigned argc, Value* vp);
+ static MOZ_MUST_USE bool get_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool get(JSContext* cx, unsigned argc, Value* vp);
+ static MOZ_MUST_USE bool has_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool set_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool set(JSContext* cx, unsigned argc, Value* vp);
+ static MOZ_MUST_USE bool delete_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool delete_(JSContext* cx, unsigned argc, Value* vp);
+ static MOZ_MUST_USE bool keys_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool keys(JSContext* cx, unsigned argc, Value* vp);
+ static MOZ_MUST_USE bool values_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool values(JSContext* cx, unsigned argc, Value* vp);
+ static MOZ_MUST_USE bool entries_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool clear_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool clear(JSContext* cx, unsigned argc, Value* vp);
+};
+
+class MapIteratorObject : public NativeObject
+{
+ public:
+ static const Class class_;
+
+ enum { TargetSlot, RangeSlot, KindSlot, SlotCount };
+
+ static_assert(TargetSlot == ITERATOR_SLOT_TARGET,
+ "TargetSlot must match self-hosting define for iterated object slot.");
+ static_assert(RangeSlot == ITERATOR_SLOT_RANGE,
+ "RangeSlot must match self-hosting define for range or index slot.");
+ static_assert(KindSlot == ITERATOR_SLOT_ITEM_KIND,
+ "KindSlot must match self-hosting define for item kind slot.");
+
+ static const JSFunctionSpec methods[];
+ static MapIteratorObject* create(JSContext* cx, HandleObject mapobj, ValueMap* data,
+ MapObject::IteratorKind kind);
+ static void finalize(FreeOp* fop, JSObject* obj);
+
+ static MOZ_MUST_USE bool next(Handle<MapIteratorObject*> mapIterator,
+ HandleArrayObject resultPairObj, JSContext* cx);
+
+ static JSObject* createResultPair(JSContext* cx);
+
+ private:
+ inline MapObject::IteratorKind kind() const;
+};
+
+class SetObject : public NativeObject {
+ public:
+ enum IteratorKind { Keys, Values, Entries };
+
+ static_assert(Keys == ITEM_KIND_KEY,
+ "IteratorKind Keys must match self-hosting define for item kind key.");
+ static_assert(Values == ITEM_KIND_VALUE,
+ "IteratorKind Values must match self-hosting define for item kind value.");
+ static_assert(Entries == ITEM_KIND_KEY_AND_VALUE,
+ "IteratorKind Entries must match self-hosting define for item kind "
+ "key-and-value.");
+
+ static JSObject* initClass(JSContext* cx, JSObject* obj);
+ static const Class class_;
+
+ enum { NurseryKeysSlot, SlotCount };
+
+ static MOZ_MUST_USE bool keys(JSContext *cx, HandleObject obj,
+ JS::MutableHandle<GCVector<JS::Value>> keys);
+ static MOZ_MUST_USE bool values(JSContext *cx, unsigned argc, Value *vp);
+ static MOZ_MUST_USE bool add(JSContext *cx, HandleObject obj, HandleValue key);
+ static MOZ_MUST_USE bool has(JSContext *cx, unsigned argc, Value *vp);
+
+ // Publicly exposed Set calls for JSAPI access (webidl maplike/setlike
+ // interfaces, etc.)
+ static SetObject* create(JSContext *cx, HandleObject proto = nullptr);
+ static uint32_t size(JSContext *cx, HandleObject obj);
+ static MOZ_MUST_USE bool has(JSContext *cx, HandleObject obj, HandleValue key, bool* rval);
+ static MOZ_MUST_USE bool clear(JSContext *cx, HandleObject obj);
+ static MOZ_MUST_USE bool iterator(JSContext *cx, IteratorKind kind, HandleObject obj,
+ MutableHandleValue iter);
+ static MOZ_MUST_USE bool delete_(JSContext *cx, HandleObject obj, HandleValue key, bool *rval);
+
+ using UnbarrieredTable = OrderedHashSet<Value, UnbarrieredHashPolicy, RuntimeAllocPolicy>;
+ friend class OrderedHashTableRef<SetObject>;
+
+ private:
+ static const ClassOps classOps_;
+
+ static const JSPropertySpec properties[];
+ static const JSFunctionSpec methods[];
+ static const JSPropertySpec staticProperties[];
+
+ ValueSet* getData() { return static_cast<ValueSet*>(getPrivate()); }
+ static ValueSet& extract(HandleObject o);
+ static ValueSet& extract(const CallArgs& args);
+ static void mark(JSTracer* trc, JSObject* obj);
+ static void finalize(FreeOp* fop, JSObject* obj);
+ static bool construct(JSContext* cx, unsigned argc, Value* vp);
+
+ static bool is(HandleValue v);
+ static bool is(HandleObject o);
+
+ static bool isBuiltinAdd(HandleValue add, JSContext* cx);
+
+ static MOZ_MUST_USE bool iterator_impl(JSContext* cx, const CallArgs& args, IteratorKind kind);
+
+ static MOZ_MUST_USE bool size_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool size(JSContext* cx, unsigned argc, Value* vp);
+ static MOZ_MUST_USE bool has_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool add_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool add(JSContext* cx, unsigned argc, Value* vp);
+ static MOZ_MUST_USE bool delete_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool delete_(JSContext* cx, unsigned argc, Value* vp);
+ static MOZ_MUST_USE bool values_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool entries_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool entries(JSContext* cx, unsigned argc, Value* vp);
+ static MOZ_MUST_USE bool clear_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool clear(JSContext* cx, unsigned argc, Value* vp);
+};
+
+class SetIteratorObject : public NativeObject
+{
+ public:
+ static const Class class_;
+
+ enum { TargetSlot, RangeSlot, KindSlot, SlotCount };
+
+ static_assert(TargetSlot == ITERATOR_SLOT_TARGET,
+ "TargetSlot must match self-hosting define for iterated object slot.");
+ static_assert(RangeSlot == ITERATOR_SLOT_RANGE,
+ "RangeSlot must match self-hosting define for range or index slot.");
+ static_assert(KindSlot == ITERATOR_SLOT_ITEM_KIND,
+ "KindSlot must match self-hosting define for item kind slot.");
+
+ static const JSFunctionSpec methods[];
+ static SetIteratorObject* create(JSContext* cx, HandleObject setobj, ValueSet* data,
+ SetObject::IteratorKind kind);
+ static void finalize(FreeOp* fop, JSObject* obj);
+
+ static MOZ_MUST_USE bool next(Handle<SetIteratorObject*> setIterator,
+ HandleArrayObject resultObj, JSContext* cx);
+
+ static JSObject* createResult(JSContext* cx);
+
+ private:
+ inline SetObject::IteratorKind kind() const;
+};
+
+using SetInitGetPrototypeOp = NativeObject* (*)(JSContext*, Handle<GlobalObject*>);
+using SetInitIsBuiltinOp = bool (*)(HandleValue, JSContext*);
+
+template <SetInitGetPrototypeOp getPrototypeOp, SetInitIsBuiltinOp isBuiltinOp>
+static MOZ_MUST_USE bool
+IsOptimizableInitForSet(JSContext* cx, HandleObject setObject, HandleValue iterable, bool* optimized)
+{
+ MOZ_ASSERT(!*optimized);
+
+ if (!iterable.isObject())
+ return true;
+
+ RootedObject array(cx, &iterable.toObject());
+ if (!IsPackedArray(array))
+ return true;
+
+ // Get the canonical prototype object.
+ RootedNativeObject setProto(cx, getPrototypeOp(cx, cx->global()));
+ if (!setProto)
+ return false;
+
+ // Ensures setObject's prototype is the canonical prototype.
+ if (setObject->staticPrototype() != setProto)
+ return true;
+
+ // Look up the 'add' value on the prototype object.
+ Shape* addShape = setProto->lookup(cx, cx->names().add);
+ if (!addShape || !addShape->hasSlot())
+ return true;
+
+ // Get the referred value, ensure it holds the canonical add function.
+ RootedValue add(cx, setProto->getSlot(addShape->slot()));
+ if (!isBuiltinOp(add, cx))
+ return true;
+
+ ForOfPIC::Chain* stubChain = ForOfPIC::getOrCreate(cx);
+ if (!stubChain)
+ return false;
+
+ return stubChain->tryOptimizeArray(cx, array.as<ArrayObject>(), optimized);
+}
+
+extern JSObject*
+InitMapClass(JSContext* cx, HandleObject obj);
+
+extern JSObject*
+InitSetClass(JSContext* cx, HandleObject obj);
+
+} /* namespace js */
+
+#endif /* builtin_MapObject_h */
diff --git a/js/src/builtin/Module.js b/js/src/builtin/Module.js
new file mode 100644
index 000000000..7b70a7fe8
--- /dev/null
+++ b/js/src/builtin/Module.js
@@ -0,0 +1,328 @@
+/* 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/. */
+
+function CallModuleResolveHook(module, specifier, expectedMinimumState)
+{
+ let requestedModule = HostResolveImportedModule(module, specifier);
+ if (requestedModule.state < expectedMinimumState)
+ ThrowInternalError(JSMSG_BAD_MODULE_STATE);
+
+ return requestedModule;
+}
+
+// 15.2.1.16.2 GetExportedNames(exportStarSet)
+function ModuleGetExportedNames(exportStarSet = [])
+{
+ if (!IsObject(this) || !IsModule(this)) {
+ return callFunction(CallModuleMethodIfWrapped, this, exportStarSet,
+ "ModuleGetExportedNames");
+ }
+
+ // Step 1
+ let module = this;
+
+ // Step 2
+ if (callFunction(ArrayIncludes, exportStarSet, module))
+ return [];
+
+ // Step 3
+ _DefineDataProperty(exportStarSet, exportStarSet.length, module);
+
+ // Step 4
+ let exportedNames = [];
+ let namesCount = 0;
+
+ // Step 5
+ let localExportEntries = module.localExportEntries;
+ for (let i = 0; i < localExportEntries.length; i++) {
+ let e = localExportEntries[i];
+ _DefineDataProperty(exportedNames, namesCount++, e.exportName);
+ }
+
+ // Step 6
+ let indirectExportEntries = module.indirectExportEntries;
+ for (let i = 0; i < indirectExportEntries.length; i++) {
+ let e = indirectExportEntries[i];
+ _DefineDataProperty(exportedNames, namesCount++, e.exportName);
+ }
+
+ // Step 7
+ let starExportEntries = module.starExportEntries;
+ for (let i = 0; i < starExportEntries.length; i++) {
+ let e = starExportEntries[i];
+ let requestedModule = CallModuleResolveHook(module, e.moduleRequest,
+ MODULE_STATE_INSTANTIATED);
+ let starNames = callFunction(requestedModule.getExportedNames, requestedModule,
+ exportStarSet);
+ for (let j = 0; j < starNames.length; j++) {
+ let n = starNames[j];
+ if (n !== "default" && !callFunction(ArrayIncludes, exportedNames, n))
+ _DefineDataProperty(exportedNames, namesCount++, n);
+ }
+ }
+
+ return exportedNames;
+}
+
+// 15.2.1.16.3 ResolveExport(exportName, resolveSet, exportStarSet)
+function ModuleResolveExport(exportName, resolveSet = [], exportStarSet = [])
+{
+ if (!IsObject(this) || !IsModule(this)) {
+ return callFunction(CallModuleMethodIfWrapped, this, exportName, resolveSet,
+ exportStarSet, "ModuleResolveExport");
+ }
+
+ // Step 1
+ let module = this;
+
+ // Step 2
+ for (let i = 0; i < resolveSet.length; i++) {
+ let r = resolveSet[i];
+ if (r.module === module && r.exportName === exportName)
+ return null;
+ }
+
+ // Step 3
+ _DefineDataProperty(resolveSet, resolveSet.length, {module: module, exportName: exportName});
+
+ // Step 4
+ let localExportEntries = module.localExportEntries;
+ for (let i = 0; i < localExportEntries.length; i++) {
+ let e = localExportEntries[i];
+ if (exportName === e.exportName)
+ return {module: module, bindingName: e.localName};
+ }
+
+ // Step 5
+ let indirectExportEntries = module.indirectExportEntries;
+ for (let i = 0; i < indirectExportEntries.length; i++) {
+ let e = indirectExportEntries[i];
+ if (exportName === e.exportName) {
+ let importedModule = CallModuleResolveHook(module, e.moduleRequest,
+ MODULE_STATE_INSTANTIATED);
+ let indirectResolution = callFunction(importedModule.resolveExport, importedModule,
+ e.importName, resolveSet, exportStarSet);
+ if (indirectResolution !== null)
+ return indirectResolution;
+ }
+ }
+
+ // Step 6
+ if (exportName === "default") {
+ // A default export cannot be provided by an export *.
+ ThrowSyntaxError(JSMSG_BAD_DEFAULT_EXPORT);
+ }
+
+ // Step 7
+ if (callFunction(ArrayIncludes, exportStarSet, module))
+ return null;
+
+ // Step 8
+ _DefineDataProperty(exportStarSet, exportStarSet.length, module);
+
+ // Step 9
+ let starResolution = null;
+
+ // Step 10
+ let starExportEntries = module.starExportEntries;
+ for (let i = 0; i < starExportEntries.length; i++) {
+ let e = starExportEntries[i];
+ let importedModule = CallModuleResolveHook(module, e.moduleRequest,
+ MODULE_STATE_INSTANTIATED);
+ let resolution = callFunction(importedModule.resolveExport, importedModule,
+ exportName, resolveSet, exportStarSet);
+ if (resolution === "ambiguous")
+ return resolution;
+
+ if (resolution !== null) {
+ if (starResolution === null) {
+ starResolution = resolution;
+ } else {
+ if (resolution.module !== starResolution.module ||
+ resolution.exportName !== starResolution.exportName)
+ {
+ return "ambiguous";
+ }
+ }
+ }
+ }
+
+ return starResolution;
+}
+
+// 15.2.1.18 GetModuleNamespace(module)
+function GetModuleNamespace(module)
+{
+ // Step 2
+ let namespace = module.namespace;
+
+ // Step 3
+ if (typeof namespace === "undefined") {
+ let exportedNames = callFunction(module.getExportedNames, module);
+ let unambiguousNames = [];
+ for (let i = 0; i < exportedNames.length; i++) {
+ let name = exportedNames[i];
+ let resolution = callFunction(module.resolveExport, module, name);
+ if (resolution === null)
+ ThrowSyntaxError(JSMSG_MISSING_NAMESPACE_EXPORT);
+ if (resolution !== "ambiguous")
+ _DefineDataProperty(unambiguousNames, unambiguousNames.length, name);
+ }
+ namespace = ModuleNamespaceCreate(module, unambiguousNames);
+ }
+
+ // Step 4
+ return namespace;
+}
+
+// 9.4.6.13 ModuleNamespaceCreate(module, exports)
+function ModuleNamespaceCreate(module, exports)
+{
+ callFunction(std_Array_sort, exports);
+
+ let ns = NewModuleNamespace(module, exports);
+
+ // Pre-compute all bindings now rather than calling ResolveExport() on every
+ // access.
+ for (let i = 0; i < exports.length; i++) {
+ let name = exports[i];
+ let binding = callFunction(module.resolveExport, module, name);
+ assert(binding !== null && binding !== "ambiguous", "Failed to resolve binding");
+ AddModuleNamespaceBinding(ns, name, binding.module, binding.bindingName);
+ }
+
+ return ns;
+}
+
+function GetModuleEnvironment(module)
+{
+ assert(IsModule(module), "Non-module passed to GetModuleEnvironment");
+
+ // Check for a previous failed attempt to instantiate this module. This can
+ // only happen due to a bug in the module loader.
+ if (module.state == MODULE_STATE_FAILED)
+ ThrowInternalError(JSMSG_MODULE_INSTANTIATE_FAILED);
+
+ let env = UnsafeGetReservedSlot(module, MODULE_OBJECT_ENVIRONMENT_SLOT);
+ assert(env === undefined || IsModuleEnvironment(env),
+ "Module environment slot contains unexpected value");
+
+ return env;
+}
+
+function RecordInstantationFailure(module)
+{
+ // Set the module's environment slot to 'null' to indicate a failed module
+ // instantiation.
+ assert(IsModule(module), "Non-module passed to RecordInstantationFailure");
+ SetModuleState(module, MODULE_STATE_FAILED);
+ UnsafeSetReservedSlot(module, MODULE_OBJECT_ENVIRONMENT_SLOT, undefined);
+}
+
+// 15.2.1.16.4 ModuleDeclarationInstantiation()
+function ModuleDeclarationInstantiation()
+{
+ if (!IsObject(this) || !IsModule(this))
+ return callFunction(CallModuleMethodIfWrapped, this, "ModuleDeclarationInstantiation");
+
+ // Step 1
+ let module = this;
+
+ // Step 5
+ if (GetModuleEnvironment(module) !== undefined)
+ return;
+
+ // Step 7
+ CreateModuleEnvironment(module);
+ let env = GetModuleEnvironment(module);
+
+ SetModuleState(this, MODULE_STATE_INSTANTIATED);
+
+ try {
+ // Step 8
+ let requestedModules = module.requestedModules;
+ for (let i = 0; i < requestedModules.length; i++) {
+ let required = requestedModules[i];
+ let requiredModule = CallModuleResolveHook(module, required, MODULE_STATE_PARSED);
+ callFunction(requiredModule.declarationInstantiation, requiredModule);
+ }
+
+ // Step 9
+ let indirectExportEntries = module.indirectExportEntries;
+ for (let i = 0; i < indirectExportEntries.length; i++) {
+ let e = indirectExportEntries[i];
+ let resolution = callFunction(module.resolveExport, module, e.exportName);
+ if (resolution === null)
+ ThrowSyntaxError(JSMSG_MISSING_INDIRECT_EXPORT, e.exportName);
+ if (resolution === "ambiguous")
+ ThrowSyntaxError(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, e.exportName);
+ }
+
+ // Step 12
+ let importEntries = module.importEntries;
+ for (let i = 0; i < importEntries.length; i++) {
+ let imp = importEntries[i];
+ let importedModule = CallModuleResolveHook(module, imp.moduleRequest,
+ MODULE_STATE_INSTANTIATED);
+ if (imp.importName === "*") {
+ let namespace = GetModuleNamespace(importedModule);
+ CreateNamespaceBinding(env, imp.localName, namespace);
+ } else {
+ let resolution = callFunction(importedModule.resolveExport, importedModule,
+ imp.importName);
+ if (resolution === null)
+ ThrowSyntaxError(JSMSG_MISSING_IMPORT, imp.importName);
+ if (resolution === "ambiguous")
+ ThrowSyntaxError(JSMSG_AMBIGUOUS_IMPORT, imp.importName);
+ CreateImportBinding(env, imp.localName, resolution.module, resolution.bindingName);
+ }
+ }
+
+ // Step 16.iv
+ InstantiateModuleFunctionDeclarations(module);
+ } catch (e) {
+ RecordInstantationFailure(module);
+ throw e;
+ }
+}
+_SetCanonicalName(ModuleDeclarationInstantiation, "ModuleDeclarationInstantiation");
+
+// 15.2.1.16.5 ModuleEvaluation()
+function ModuleEvaluation()
+{
+ if (!IsObject(this) || !IsModule(this))
+ return callFunction(CallModuleMethodIfWrapped, this, "ModuleEvaluation");
+
+ // Step 1
+ let module = this;
+
+ if (module.state < MODULE_STATE_INSTANTIATED)
+ ThrowInternalError(JSMSG_BAD_MODULE_STATE);
+
+ // Step 4
+ if (module.state == MODULE_STATE_EVALUATED)
+ return undefined;
+
+ // Step 5
+ SetModuleState(this, MODULE_STATE_EVALUATED);
+
+ // Step 6
+ let requestedModules = module.requestedModules;
+ for (let i = 0; i < requestedModules.length; i++) {
+ let required = requestedModules[i];
+ let requiredModule = CallModuleResolveHook(module, required, MODULE_STATE_INSTANTIATED);
+ callFunction(requiredModule.evaluation, requiredModule);
+ }
+
+ return EvaluateModule(module);
+}
+_SetCanonicalName(ModuleEvaluation, "ModuleEvaluation");
+
+function ModuleNamespaceEnumerate()
+{
+ if (!IsObject(this) || !IsModuleNamespace(this))
+ return callFunction(CallModuleMethodIfWrapped, this, "ModuleNamespaceEnumerate");
+
+ return CreateListIterator(ModuleNamespaceExports(this));
+}
diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp
new file mode 100644
index 000000000..3bfc8f60b
--- /dev/null
+++ b/js/src/builtin/ModuleObject.cpp
@@ -0,0 +1,1331 @@
+/* -*- 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 "builtin/ModuleObject.h"
+
+#include "builtin/SelfHostingDefines.h"
+#include "frontend/ParseNode.h"
+#include "frontend/SharedContext.h"
+#include "gc/Policy.h"
+#include "gc/Tracer.h"
+
+#include "jsobjinlines.h"
+#include "jsscriptinlines.h"
+
+using namespace js;
+using namespace js::frontend;
+
+static_assert(MODULE_STATE_FAILED < MODULE_STATE_PARSED &&
+ MODULE_STATE_PARSED < MODULE_STATE_INSTANTIATED &&
+ MODULE_STATE_INSTANTIATED < MODULE_STATE_EVALUATED,
+ "Module states are ordered incorrectly");
+
+template<typename T, Value ValueGetter(const T* obj)>
+static bool
+ModuleValueGetterImpl(JSContext* cx, const CallArgs& args)
+{
+ args.rval().set(ValueGetter(&args.thisv().toObject().as<T>()));
+ return true;
+}
+
+template<typename T, Value ValueGetter(const T* obj)>
+static bool
+ModuleValueGetter(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<T::isInstance, ModuleValueGetterImpl<T, ValueGetter>>(cx, args);
+}
+
+#define DEFINE_GETTER_FUNCTIONS(cls, name, slot) \
+ static Value \
+ cls##_##name##Value(const cls* obj) { \
+ return obj->getFixedSlot(cls::slot); \
+ } \
+ \
+ static bool \
+ cls##_##name##Getter(JSContext* cx, unsigned argc, Value* vp) \
+ { \
+ return ModuleValueGetter<cls, cls##_##name##Value>(cx, argc, vp); \
+ }
+
+#define DEFINE_ATOM_ACCESSOR_METHOD(cls, name) \
+ JSAtom* \
+ cls::name() const \
+ { \
+ Value value = cls##_##name##Value(this); \
+ return &value.toString()->asAtom(); \
+ }
+
+#define DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(cls, name) \
+ JSAtom* \
+ cls::name() const \
+ { \
+ Value value = cls##_##name##Value(this); \
+ if (value.isNull()) \
+ return nullptr; \
+ return &value.toString()->asAtom(); \
+ }
+
+///////////////////////////////////////////////////////////////////////////
+// ImportEntryObject
+
+/* static */ const Class
+ImportEntryObject::class_ = {
+ "ImportEntry",
+ JSCLASS_HAS_RESERVED_SLOTS(ImportEntryObject::SlotCount) |
+ JSCLASS_IS_ANONYMOUS
+};
+
+DEFINE_GETTER_FUNCTIONS(ImportEntryObject, moduleRequest, ModuleRequestSlot)
+DEFINE_GETTER_FUNCTIONS(ImportEntryObject, importName, ImportNameSlot)
+DEFINE_GETTER_FUNCTIONS(ImportEntryObject, localName, LocalNameSlot)
+
+DEFINE_ATOM_ACCESSOR_METHOD(ImportEntryObject, moduleRequest)
+DEFINE_ATOM_ACCESSOR_METHOD(ImportEntryObject, importName)
+DEFINE_ATOM_ACCESSOR_METHOD(ImportEntryObject, localName)
+
+/* static */ bool
+ImportEntryObject::isInstance(HandleValue value)
+{
+ return value.isObject() && value.toObject().is<ImportEntryObject>();
+}
+
+/* static */ bool
+GlobalObject::initImportEntryProto(JSContext* cx, Handle<GlobalObject*> global)
+{
+ static const JSPropertySpec protoAccessors[] = {
+ JS_PSG("moduleRequest", ImportEntryObject_moduleRequestGetter, 0),
+ JS_PSG("importName", ImportEntryObject_importNameGetter, 0),
+ JS_PSG("localName", ImportEntryObject_localNameGetter, 0),
+ JS_PS_END
+ };
+
+ RootedObject proto(cx, global->createBlankPrototype<PlainObject>(cx));
+ if (!proto)
+ return false;
+
+ if (!DefinePropertiesAndFunctions(cx, proto, protoAccessors, nullptr))
+ return false;
+
+ global->setReservedSlot(IMPORT_ENTRY_PROTO, ObjectValue(*proto));
+ return true;
+}
+
+/* static */ ImportEntryObject*
+ImportEntryObject::create(ExclusiveContext* cx,
+ HandleAtom moduleRequest,
+ HandleAtom importName,
+ HandleAtom localName)
+{
+ RootedObject proto(cx, cx->global()->getImportEntryPrototype());
+ RootedObject obj(cx, NewObjectWithGivenProto(cx, &class_, proto));
+ if (!obj)
+ return nullptr;
+
+ RootedImportEntryObject self(cx, &obj->as<ImportEntryObject>());
+ self->initReservedSlot(ModuleRequestSlot, StringValue(moduleRequest));
+ self->initReservedSlot(ImportNameSlot, StringValue(importName));
+ self->initReservedSlot(LocalNameSlot, StringValue(localName));
+ return self;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// ExportEntryObject
+
+/* static */ const Class
+ExportEntryObject::class_ = {
+ "ExportEntry",
+ JSCLASS_HAS_RESERVED_SLOTS(ExportEntryObject::SlotCount) |
+ JSCLASS_IS_ANONYMOUS
+};
+
+DEFINE_GETTER_FUNCTIONS(ExportEntryObject, exportName, ExportNameSlot)
+DEFINE_GETTER_FUNCTIONS(ExportEntryObject, moduleRequest, ModuleRequestSlot)
+DEFINE_GETTER_FUNCTIONS(ExportEntryObject, importName, ImportNameSlot)
+DEFINE_GETTER_FUNCTIONS(ExportEntryObject, localName, LocalNameSlot)
+
+DEFINE_ATOM_ACCESSOR_METHOD(ExportEntryObject, exportName)
+DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ExportEntryObject, moduleRequest)
+DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ExportEntryObject, importName)
+DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ExportEntryObject, localName)
+
+/* static */ bool
+ExportEntryObject::isInstance(HandleValue value)
+{
+ return value.isObject() && value.toObject().is<ExportEntryObject>();
+}
+
+/* static */ bool
+GlobalObject::initExportEntryProto(JSContext* cx, Handle<GlobalObject*> global)
+{
+ static const JSPropertySpec protoAccessors[] = {
+ JS_PSG("exportName", ExportEntryObject_exportNameGetter, 0),
+ JS_PSG("moduleRequest", ExportEntryObject_moduleRequestGetter, 0),
+ JS_PSG("importName", ExportEntryObject_importNameGetter, 0),
+ JS_PSG("localName", ExportEntryObject_localNameGetter, 0),
+ JS_PS_END
+ };
+
+ RootedObject proto(cx, global->createBlankPrototype<PlainObject>(cx));
+ if (!proto)
+ return false;
+
+ if (!DefinePropertiesAndFunctions(cx, proto, protoAccessors, nullptr))
+ return false;
+
+ global->setReservedSlot(EXPORT_ENTRY_PROTO, ObjectValue(*proto));
+ return true;
+}
+
+static Value
+StringOrNullValue(JSString* maybeString)
+{
+ return maybeString ? StringValue(maybeString) : NullValue();
+}
+
+/* static */ ExportEntryObject*
+ExportEntryObject::create(ExclusiveContext* cx,
+ HandleAtom maybeExportName,
+ HandleAtom maybeModuleRequest,
+ HandleAtom maybeImportName,
+ HandleAtom maybeLocalName)
+{
+ RootedObject proto(cx, cx->global()->getExportEntryPrototype());
+ RootedObject obj(cx, NewObjectWithGivenProto(cx, &class_, proto));
+ if (!obj)
+ return nullptr;
+
+ RootedExportEntryObject self(cx, &obj->as<ExportEntryObject>());
+ self->initReservedSlot(ExportNameSlot, StringOrNullValue(maybeExportName));
+ self->initReservedSlot(ModuleRequestSlot, StringOrNullValue(maybeModuleRequest));
+ self->initReservedSlot(ImportNameSlot, StringOrNullValue(maybeImportName));
+ self->initReservedSlot(LocalNameSlot, StringOrNullValue(maybeLocalName));
+ return self;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// IndirectBindingMap
+
+IndirectBindingMap::Binding::Binding(ModuleEnvironmentObject* environment, Shape* shape)
+ : environment(environment), shape(shape)
+{}
+
+IndirectBindingMap::IndirectBindingMap(Zone* zone)
+ : map_(ZoneAllocPolicy(zone))
+{
+}
+
+bool
+IndirectBindingMap::init()
+{
+ return map_.init();
+}
+
+void
+IndirectBindingMap::trace(JSTracer* trc)
+{
+ for (Map::Enum e(map_); !e.empty(); e.popFront()) {
+ Binding& b = e.front().value();
+ TraceEdge(trc, &b.environment, "module bindings environment");
+ TraceEdge(trc, &b.shape, "module bindings shape");
+ jsid bindingName = e.front().key();
+ TraceManuallyBarrieredEdge(trc, &bindingName, "module bindings binding name");
+ MOZ_ASSERT(bindingName == e.front().key());
+ }
+}
+
+bool
+IndirectBindingMap::putNew(JSContext* cx, HandleId name,
+ HandleModuleEnvironmentObject environment, HandleId localName)
+{
+ RootedShape shape(cx, environment->lookup(cx, localName));
+ MOZ_ASSERT(shape);
+ if (!map_.putNew(name, Binding(environment, shape))) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+IndirectBindingMap::lookup(jsid name, ModuleEnvironmentObject** envOut, Shape** shapeOut) const
+{
+ auto ptr = map_.lookup(name);
+ if (!ptr)
+ return false;
+
+ const Binding& binding = ptr->value();
+ MOZ_ASSERT(binding.environment);
+ MOZ_ASSERT(!binding.environment->inDictionaryMode());
+ MOZ_ASSERT(binding.environment->containsPure(binding.shape));
+ *envOut = binding.environment;
+ *shapeOut = binding.shape;
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// ModuleNamespaceObject
+
+/* static */ const ModuleNamespaceObject::ProxyHandler ModuleNamespaceObject::proxyHandler;
+
+/* static */ bool
+ModuleNamespaceObject::isInstance(HandleValue value)
+{
+ return value.isObject() && value.toObject().is<ModuleNamespaceObject>();
+}
+
+/* static */ ModuleNamespaceObject*
+ModuleNamespaceObject::create(JSContext* cx, HandleModuleObject module)
+{
+ RootedValue priv(cx, ObjectValue(*module));
+ ProxyOptions options;
+ options.setLazyProto(true);
+ options.setSingleton(true);
+ RootedObject object(cx, NewProxyObject(cx, &proxyHandler, priv, nullptr, options));
+ if (!object)
+ return nullptr;
+
+ RootedId funName(cx, INTERNED_STRING_TO_JSID(cx, cx->names().Symbol_iterator_fun));
+ RootedFunction enumerateFun(cx);
+ enumerateFun = JS::GetSelfHostedFunction(cx, "ModuleNamespaceEnumerate", funName, 0);
+ if (!enumerateFun)
+ return nullptr;
+
+ SetProxyExtra(object, ProxyHandler::EnumerateFunctionSlot, ObjectValue(*enumerateFun));
+
+ return &object->as<ModuleNamespaceObject>();
+}
+
+ModuleObject&
+ModuleNamespaceObject::module()
+{
+ return GetProxyPrivate(this).toObject().as<ModuleObject>();
+}
+
+JSObject&
+ModuleNamespaceObject::exports()
+{
+ JSObject* exports = module().namespaceExports();
+ MOZ_ASSERT(exports);
+ return *exports;
+}
+
+IndirectBindingMap&
+ModuleNamespaceObject::bindings()
+{
+ IndirectBindingMap* bindings = module().namespaceBindings();
+ MOZ_ASSERT(bindings);
+ return *bindings;
+}
+
+bool
+ModuleNamespaceObject::addBinding(JSContext* cx, HandleAtom exportedName,
+ HandleModuleObject targetModule, HandleAtom localName)
+{
+ IndirectBindingMap* bindings(this->module().namespaceBindings());
+ MOZ_ASSERT(bindings);
+
+ RootedModuleEnvironmentObject environment(cx, &targetModule->initialEnvironment());
+ RootedId exportedNameId(cx, AtomToId(exportedName));
+ RootedId localNameId(cx, AtomToId(localName));
+ return bindings->putNew(cx, exportedNameId, environment, localNameId);
+}
+
+const char ModuleNamespaceObject::ProxyHandler::family = 0;
+
+ModuleNamespaceObject::ProxyHandler::ProxyHandler()
+ : BaseProxyHandler(&family, true)
+{}
+
+JS::Value ModuleNamespaceObject::ProxyHandler::getEnumerateFunction(HandleObject proxy) const
+{
+ return GetProxyExtra(proxy, EnumerateFunctionSlot);
+}
+
+bool
+ModuleNamespaceObject::ProxyHandler::getPrototype(JSContext* cx, HandleObject proxy,
+ MutableHandleObject protop) const
+{
+ protop.set(nullptr);
+ return true;
+}
+
+bool
+ModuleNamespaceObject::ProxyHandler::setPrototype(JSContext* cx, HandleObject proxy,
+ HandleObject proto, ObjectOpResult& result) const
+{
+ return result.failCantSetProto();
+}
+
+bool
+ModuleNamespaceObject::ProxyHandler::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy,
+ bool* isOrdinary,
+ MutableHandleObject protop) const
+{
+ *isOrdinary = false;
+ return true;
+}
+
+bool
+ModuleNamespaceObject::ProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy,
+ bool* succeeded) const
+{
+ *succeeded = true;
+ return true;
+}
+
+bool
+ModuleNamespaceObject::ProxyHandler::isExtensible(JSContext* cx, HandleObject proxy,
+ bool* extensible) const
+{
+ *extensible = false;
+ return true;
+}
+
+bool
+ModuleNamespaceObject::ProxyHandler::preventExtensions(JSContext* cx, HandleObject proxy,
+ ObjectOpResult& result) const
+{
+ result.succeed();
+ return true;
+}
+
+bool
+ModuleNamespaceObject::ProxyHandler::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy,
+ HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const
+{
+ Rooted<ModuleNamespaceObject*> ns(cx, &proxy->as<ModuleNamespaceObject>());
+ if (JSID_IS_SYMBOL(id)) {
+ Rooted<JS::Symbol*> symbol(cx, JSID_TO_SYMBOL(id));
+ if (symbol == cx->wellKnownSymbols().iterator) {
+ RootedValue enumerateFun(cx, getEnumerateFunction(proxy));
+ desc.object().set(proxy);
+ desc.setConfigurable(false);
+ desc.setEnumerable(false);
+ desc.setValue(enumerateFun);
+ return true;
+ }
+
+ if (symbol == cx->wellKnownSymbols().toStringTag) {
+ RootedValue value(cx, StringValue(cx->names().Module));
+ desc.object().set(proxy);
+ desc.setWritable(false);
+ desc.setEnumerable(false);
+ desc.setConfigurable(true);
+ desc.setValue(value);
+ return true;
+ }
+
+ return true;
+ }
+
+ const IndirectBindingMap& bindings = ns->bindings();
+ ModuleEnvironmentObject* env;
+ Shape* shape;
+ if (!bindings.lookup(id, &env, &shape))
+ return true;
+
+ RootedValue value(cx, env->getSlot(shape->slot()));
+ if (value.isMagic(JS_UNINITIALIZED_LEXICAL)) {
+ ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, id);
+ return false;
+ }
+
+ desc.object().set(env);
+ desc.setConfigurable(false);
+ desc.setEnumerable(true);
+ desc.setValue(value);
+ return true;
+}
+
+bool
+ModuleNamespaceObject::ProxyHandler::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const
+{
+ return result.failReadOnly();
+}
+
+bool
+ModuleNamespaceObject::ProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id,
+ bool* bp) const
+{
+ Rooted<ModuleNamespaceObject*> ns(cx, &proxy->as<ModuleNamespaceObject>());
+ if (JSID_IS_SYMBOL(id)) {
+ Rooted<JS::Symbol*> symbol(cx, JSID_TO_SYMBOL(id));
+ return symbol == cx->wellKnownSymbols().iterator ||
+ symbol == cx->wellKnownSymbols().toStringTag;
+ }
+
+ *bp = ns->bindings().has(id);
+ return true;
+}
+
+bool
+ModuleNamespaceObject::ProxyHandler::get(JSContext* cx, HandleObject proxy, HandleValue receiver,
+ HandleId id, MutableHandleValue vp) const
+{
+ Rooted<ModuleNamespaceObject*> ns(cx, &proxy->as<ModuleNamespaceObject>());
+ if (JSID_IS_SYMBOL(id)) {
+ Rooted<JS::Symbol*> symbol(cx, JSID_TO_SYMBOL(id));
+ if (symbol == cx->wellKnownSymbols().iterator) {
+ vp.set(getEnumerateFunction(proxy));
+ return true;
+ }
+
+ if (symbol == cx->wellKnownSymbols().toStringTag) {
+ vp.setString(cx->names().Module);
+ return true;
+ }
+
+ return false;
+ }
+
+ ModuleEnvironmentObject* env;
+ Shape* shape;
+ if (!ns->bindings().lookup(id, &env, &shape))
+ return false;
+
+ RootedValue value(cx, env->getSlot(shape->slot()));
+ if (value.isMagic(JS_UNINITIALIZED_LEXICAL)) {
+ ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, id);
+ return false;
+ }
+
+ vp.set(value);
+ return true;
+}
+
+bool
+ModuleNamespaceObject::ProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result) const
+{
+ return result.failReadOnly();
+}
+
+bool
+ModuleNamespaceObject::ProxyHandler::delete_(JSContext* cx, HandleObject proxy, HandleId id,
+ ObjectOpResult& result) const
+{
+ Rooted<ModuleNamespaceObject*> ns(cx, &proxy->as<ModuleNamespaceObject>());
+ if (ns->bindings().has(id))
+ return result.failReadOnly();
+
+ return result.succeed();
+}
+
+bool
+ModuleNamespaceObject::ProxyHandler::ownPropertyKeys(JSContext* cx, HandleObject proxy,
+ AutoIdVector& props) const
+{
+ Rooted<ModuleNamespaceObject*> ns(cx, &proxy->as<ModuleNamespaceObject>());
+ RootedObject exports(cx, &ns->exports());
+ uint32_t count;
+ if (!GetLengthProperty(cx, exports, &count) || !props.reserve(props.length() + count))
+ return false;
+
+ Rooted<ValueVector> names(cx, ValueVector(cx));
+ if (!names.resize(count) || !GetElements(cx, exports, count, names.begin()))
+ return false;
+
+ for (uint32_t i = 0; i < count; i++)
+ props.infallibleAppend(AtomToId(&names[i].toString()->asAtom()));
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// FunctionDeclaration
+
+FunctionDeclaration::FunctionDeclaration(HandleAtom name, HandleFunction fun)
+ : name(name), fun(fun)
+{}
+
+void FunctionDeclaration::trace(JSTracer* trc)
+{
+ TraceEdge(trc, &name, "FunctionDeclaration name");
+ TraceEdge(trc, &fun, "FunctionDeclaration fun");
+}
+
+///////////////////////////////////////////////////////////////////////////
+// ModuleObject
+
+/* static */ const ClassOps
+ModuleObject::classOps_ = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ ModuleObject::finalize,
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ nullptr, /* construct */
+ ModuleObject::trace
+};
+
+/* static */ const Class
+ModuleObject::class_ = {
+ "Module",
+ JSCLASS_HAS_RESERVED_SLOTS(ModuleObject::SlotCount) |
+ JSCLASS_IS_ANONYMOUS |
+ JSCLASS_BACKGROUND_FINALIZE,
+ &ModuleObject::classOps_
+};
+
+#define DEFINE_ARRAY_SLOT_ACCESSOR(cls, name, slot) \
+ ArrayObject& \
+ cls::name() const \
+ { \
+ return getFixedSlot(cls::slot).toObject().as<ArrayObject>(); \
+ }
+
+DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, requestedModules, RequestedModulesSlot)
+DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, importEntries, ImportEntriesSlot)
+DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, localExportEntries, LocalExportEntriesSlot)
+DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, indirectExportEntries, IndirectExportEntriesSlot)
+DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, starExportEntries, StarExportEntriesSlot)
+
+/* static */ bool
+ModuleObject::isInstance(HandleValue value)
+{
+ return value.isObject() && value.toObject().is<ModuleObject>();
+}
+
+/* static */ ModuleObject*
+ModuleObject::create(ExclusiveContext* cx)
+{
+ RootedObject proto(cx, cx->global()->getModulePrototype());
+ RootedObject obj(cx, NewObjectWithGivenProto(cx, &class_, proto));
+ if (!obj)
+ return nullptr;
+
+ RootedModuleObject self(cx, &obj->as<ModuleObject>());
+
+ Zone* zone = cx->zone();
+ IndirectBindingMap* bindings = zone->new_<IndirectBindingMap>(zone);
+ if (!bindings || !bindings->init()) {
+ ReportOutOfMemory(cx);
+ js_delete<IndirectBindingMap>(bindings);
+ return nullptr;
+ }
+
+ self->initReservedSlot(ImportBindingsSlot, PrivateValue(bindings));
+
+ FunctionDeclarationVector* funDecls = zone->new_<FunctionDeclarationVector>(zone);
+ if (!funDecls) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ self->initReservedSlot(FunctionDeclarationsSlot, PrivateValue(funDecls));
+ return self;
+}
+
+/* static */ void
+ModuleObject::finalize(js::FreeOp* fop, JSObject* obj)
+{
+ MOZ_ASSERT(fop->maybeOffMainThread());
+ ModuleObject* self = &obj->as<ModuleObject>();
+ if (self->hasImportBindings())
+ fop->delete_(&self->importBindings());
+ if (IndirectBindingMap* bindings = self->namespaceBindings())
+ fop->delete_(bindings);
+ if (FunctionDeclarationVector* funDecls = self->functionDeclarations())
+ fop->delete_(funDecls);
+}
+
+ModuleEnvironmentObject*
+ModuleObject::environment() const
+{
+ Value value = getReservedSlot(EnvironmentSlot);
+ if (value.isUndefined())
+ return nullptr;
+
+ return &value.toObject().as<ModuleEnvironmentObject>();
+}
+
+bool
+ModuleObject::hasImportBindings() const
+{
+ // Import bindings may not be present if we hit OOM in initialization.
+ return !getReservedSlot(ImportBindingsSlot).isUndefined();
+}
+
+IndirectBindingMap&
+ModuleObject::importBindings()
+{
+ return *static_cast<IndirectBindingMap*>(getReservedSlot(ImportBindingsSlot).toPrivate());
+}
+
+JSObject*
+ModuleObject::namespaceExports()
+{
+ Value value = getReservedSlot(NamespaceExportsSlot);
+ if (value.isUndefined())
+ return nullptr;
+
+ return &value.toObject();
+}
+
+IndirectBindingMap*
+ModuleObject::namespaceBindings()
+{
+ Value value = getReservedSlot(NamespaceBindingsSlot);
+ if (value.isUndefined())
+ return nullptr;
+
+ return static_cast<IndirectBindingMap*>(value.toPrivate());
+}
+
+ModuleNamespaceObject*
+ModuleObject::namespace_()
+{
+ Value value = getReservedSlot(NamespaceSlot);
+ if (value.isUndefined())
+ return nullptr;
+ return &value.toObject().as<ModuleNamespaceObject>();
+}
+
+FunctionDeclarationVector*
+ModuleObject::functionDeclarations()
+{
+ Value value = getReservedSlot(FunctionDeclarationsSlot);
+ if (value.isUndefined())
+ return nullptr;
+
+ return static_cast<FunctionDeclarationVector*>(value.toPrivate());
+}
+
+void
+ModuleObject::init(HandleScript script)
+{
+ initReservedSlot(ScriptSlot, PrivateValue(script));
+ initReservedSlot(StateSlot, Int32Value(MODULE_STATE_FAILED));
+}
+
+void
+ModuleObject::setInitialEnvironment(HandleModuleEnvironmentObject initialEnvironment)
+{
+ initReservedSlot(InitialEnvironmentSlot, ObjectValue(*initialEnvironment));
+}
+
+void
+ModuleObject::initImportExportData(HandleArrayObject requestedModules,
+ HandleArrayObject importEntries,
+ HandleArrayObject localExportEntries,
+ HandleArrayObject indirectExportEntries,
+ HandleArrayObject starExportEntries)
+{
+ initReservedSlot(RequestedModulesSlot, ObjectValue(*requestedModules));
+ initReservedSlot(ImportEntriesSlot, ObjectValue(*importEntries));
+ initReservedSlot(LocalExportEntriesSlot, ObjectValue(*localExportEntries));
+ initReservedSlot(IndirectExportEntriesSlot, ObjectValue(*indirectExportEntries));
+ initReservedSlot(StarExportEntriesSlot, ObjectValue(*starExportEntries));
+ setReservedSlot(StateSlot, Int32Value(MODULE_STATE_PARSED));
+}
+
+static bool
+FreezeObjectProperty(JSContext* cx, HandleNativeObject obj, uint32_t slot)
+{
+ RootedObject property(cx, &obj->getSlot(slot).toObject());
+ return FreezeObject(cx, property);
+}
+
+/* static */ bool
+ModuleObject::Freeze(JSContext* cx, HandleModuleObject self)
+{
+ return FreezeObjectProperty(cx, self, RequestedModulesSlot) &&
+ FreezeObjectProperty(cx, self, ImportEntriesSlot) &&
+ FreezeObjectProperty(cx, self, LocalExportEntriesSlot) &&
+ FreezeObjectProperty(cx, self, IndirectExportEntriesSlot) &&
+ FreezeObjectProperty(cx, self, StarExportEntriesSlot) &&
+ FreezeObject(cx, self);
+}
+
+#ifdef DEBUG
+
+static inline bool
+IsObjectFrozen(JSContext* cx, HandleObject obj)
+{
+ bool frozen = false;
+ MOZ_ALWAYS_TRUE(TestIntegrityLevel(cx, obj, IntegrityLevel::Frozen, &frozen));
+ return frozen;
+}
+
+static inline bool
+IsObjectPropertyFrozen(JSContext* cx, HandleNativeObject obj, uint32_t slot)
+{
+ RootedObject property(cx, &obj->getSlot(slot).toObject());
+ return IsObjectFrozen(cx, property);
+}
+
+/* static */ inline bool
+ModuleObject::IsFrozen(JSContext* cx, HandleModuleObject self)
+{
+ return IsObjectPropertyFrozen(cx, self, RequestedModulesSlot) &&
+ IsObjectPropertyFrozen(cx, self, ImportEntriesSlot) &&
+ IsObjectPropertyFrozen(cx, self, LocalExportEntriesSlot) &&
+ IsObjectPropertyFrozen(cx, self, IndirectExportEntriesSlot) &&
+ IsObjectPropertyFrozen(cx, self, StarExportEntriesSlot) &&
+ IsObjectFrozen(cx, self);
+}
+
+#endif
+
+inline static void
+AssertModuleScopesMatch(ModuleObject* module)
+{
+ MOZ_ASSERT(module->enclosingScope()->is<GlobalScope>());
+ MOZ_ASSERT(IsGlobalLexicalEnvironment(&module->initialEnvironment().enclosingEnvironment()));
+}
+
+void
+ModuleObject::fixEnvironmentsAfterCompartmentMerge(JSContext* cx)
+{
+ AssertModuleScopesMatch(this);
+ initialEnvironment().fixEnclosingEnvironmentAfterCompartmentMerge(script()->global());
+ AssertModuleScopesMatch(this);
+}
+
+bool
+ModuleObject::hasScript() const
+{
+ // When modules are parsed via the Reflect.parse() API, the module object
+ // doesn't have a script.
+ return !getReservedSlot(ScriptSlot).isUndefined();
+}
+
+JSScript*
+ModuleObject::script() const
+{
+ return static_cast<JSScript*>(getReservedSlot(ScriptSlot).toPrivate());
+}
+
+static inline void
+AssertValidModuleState(ModuleState state)
+{
+ MOZ_ASSERT(state >= MODULE_STATE_FAILED && state <= MODULE_STATE_EVALUATED);
+}
+
+ModuleState
+ModuleObject::state() const
+{
+ ModuleState state = getReservedSlot(StateSlot).toInt32();
+ AssertValidModuleState(state);
+ return state;
+}
+
+Value
+ModuleObject::hostDefinedField() const
+{
+ return getReservedSlot(HostDefinedSlot);
+}
+
+void
+ModuleObject::setHostDefinedField(const JS::Value& value)
+{
+ setReservedSlot(HostDefinedSlot, value);
+}
+
+ModuleEnvironmentObject&
+ModuleObject::initialEnvironment() const
+{
+ return getReservedSlot(InitialEnvironmentSlot).toObject().as<ModuleEnvironmentObject>();
+}
+
+Scope*
+ModuleObject::enclosingScope() const
+{
+ return script()->enclosingScope();
+}
+
+/* static */ void
+ModuleObject::trace(JSTracer* trc, JSObject* obj)
+{
+ ModuleObject& module = obj->as<ModuleObject>();
+ if (module.hasScript()) {
+ JSScript* script = module.script();
+ TraceManuallyBarrieredEdge(trc, &script, "Module script");
+ module.setReservedSlot(ScriptSlot, PrivateValue(script));
+ }
+
+ if (module.hasImportBindings())
+ module.importBindings().trace(trc);
+ if (IndirectBindingMap* bindings = module.namespaceBindings())
+ bindings->trace(trc);
+
+ if (FunctionDeclarationVector* funDecls = module.functionDeclarations())
+ funDecls->trace(trc);
+}
+
+void
+ModuleObject::createEnvironment()
+{
+ // The environment has already been created, we just neet to set it in the
+ // right slot.
+ MOZ_ASSERT(!getReservedSlot(InitialEnvironmentSlot).isUndefined());
+ MOZ_ASSERT(getReservedSlot(EnvironmentSlot).isUndefined());
+ setReservedSlot(EnvironmentSlot, getReservedSlot(InitialEnvironmentSlot));
+}
+
+bool
+ModuleObject::noteFunctionDeclaration(ExclusiveContext* cx, HandleAtom name, HandleFunction fun)
+{
+ FunctionDeclarationVector* funDecls = functionDeclarations();
+ if (!funDecls->emplaceBack(name, fun)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ return true;
+}
+
+/* static */ bool
+ModuleObject::instantiateFunctionDeclarations(JSContext* cx, HandleModuleObject self)
+{
+ MOZ_ASSERT(IsFrozen(cx, self));
+
+ FunctionDeclarationVector* funDecls = self->functionDeclarations();
+ if (!funDecls) {
+ JS_ReportErrorASCII(cx, "Module function declarations have already been instantiated");
+ return false;
+ }
+
+ RootedModuleEnvironmentObject env(cx, &self->initialEnvironment());
+ RootedFunction fun(cx);
+ RootedValue value(cx);
+
+ for (const auto& funDecl : *funDecls) {
+ fun = funDecl.fun;
+ RootedObject obj(cx, Lambda(cx, fun, env));
+ if (!obj)
+ return false;
+
+ value = ObjectValue(*fun);
+ if (!SetProperty(cx, env, funDecl.name->asPropertyName(), value))
+ return false;
+ }
+
+ js_delete(funDecls);
+ self->setReservedSlot(FunctionDeclarationsSlot, UndefinedValue());
+ return true;
+}
+
+void
+ModuleObject::setState(int32_t newState)
+{
+ AssertValidModuleState(newState);
+ MOZ_ASSERT(state() != MODULE_STATE_FAILED);
+ MOZ_ASSERT(newState == MODULE_STATE_FAILED || newState > state());
+ setReservedSlot(StateSlot, Int32Value(newState));
+}
+
+/* static */ bool
+ModuleObject::evaluate(JSContext* cx, HandleModuleObject self, MutableHandleValue rval)
+{
+ MOZ_ASSERT(IsFrozen(cx, self));
+
+ RootedScript script(cx, self->script());
+ RootedModuleEnvironmentObject scope(cx, self->environment());
+ if (!scope) {
+ JS_ReportErrorASCII(cx, "Module declarations have not yet been instantiated");
+ return false;
+ }
+
+ return Execute(cx, script, *scope, rval.address());
+}
+
+/* static */ ModuleNamespaceObject*
+ModuleObject::createNamespace(JSContext* cx, HandleModuleObject self, HandleObject exports)
+{
+ MOZ_ASSERT(!self->namespace_());
+ MOZ_ASSERT(exports->is<ArrayObject>() || exports->is<UnboxedArrayObject>());
+
+ RootedModuleNamespaceObject ns(cx, ModuleNamespaceObject::create(cx, self));
+ if (!ns)
+ return nullptr;
+
+ Zone* zone = cx->zone();
+ IndirectBindingMap* bindings = zone->new_<IndirectBindingMap>(zone);
+ if (!bindings || !bindings->init()) {
+ ReportOutOfMemory(cx);
+ js_delete<IndirectBindingMap>(bindings);
+ return nullptr;
+ }
+
+ self->initReservedSlot(NamespaceSlot, ObjectValue(*ns));
+ self->initReservedSlot(NamespaceExportsSlot, ObjectValue(*exports));
+ self->initReservedSlot(NamespaceBindingsSlot, PrivateValue(bindings));
+ return ns;
+}
+
+static bool
+InvokeSelfHostedMethod(JSContext* cx, HandleModuleObject self, HandlePropertyName name)
+{
+ RootedValue fval(cx);
+ if (!GlobalObject::getSelfHostedFunction(cx, cx->global(), name, name, 0, &fval))
+ return false;
+
+ RootedValue ignored(cx);
+ return Call(cx, fval, self, &ignored);
+}
+
+/* static */ bool
+ModuleObject::DeclarationInstantiation(JSContext* cx, HandleModuleObject self)
+{
+ return InvokeSelfHostedMethod(cx, self, cx->names().ModuleDeclarationInstantiation);
+}
+
+/* static */ bool
+ModuleObject::Evaluation(JSContext* cx, HandleModuleObject self)
+{
+ return InvokeSelfHostedMethod(cx, self, cx->names().ModuleEvaluation);
+}
+
+DEFINE_GETTER_FUNCTIONS(ModuleObject, namespace_, NamespaceSlot)
+DEFINE_GETTER_FUNCTIONS(ModuleObject, state, StateSlot)
+DEFINE_GETTER_FUNCTIONS(ModuleObject, requestedModules, RequestedModulesSlot)
+DEFINE_GETTER_FUNCTIONS(ModuleObject, importEntries, ImportEntriesSlot)
+DEFINE_GETTER_FUNCTIONS(ModuleObject, localExportEntries, LocalExportEntriesSlot)
+DEFINE_GETTER_FUNCTIONS(ModuleObject, indirectExportEntries, IndirectExportEntriesSlot)
+DEFINE_GETTER_FUNCTIONS(ModuleObject, starExportEntries, StarExportEntriesSlot)
+
+/* static */ bool
+GlobalObject::initModuleProto(JSContext* cx, Handle<GlobalObject*> global)
+{
+ static const JSPropertySpec protoAccessors[] = {
+ JS_PSG("namespace", ModuleObject_namespace_Getter, 0),
+ JS_PSG("state", ModuleObject_stateGetter, 0),
+ JS_PSG("requestedModules", ModuleObject_requestedModulesGetter, 0),
+ JS_PSG("importEntries", ModuleObject_importEntriesGetter, 0),
+ JS_PSG("localExportEntries", ModuleObject_localExportEntriesGetter, 0),
+ JS_PSG("indirectExportEntries", ModuleObject_indirectExportEntriesGetter, 0),
+ JS_PSG("starExportEntries", ModuleObject_starExportEntriesGetter, 0),
+ JS_PS_END
+ };
+
+ static const JSFunctionSpec protoFunctions[] = {
+ JS_SELF_HOSTED_FN("getExportedNames", "ModuleGetExportedNames", 1, 0),
+ JS_SELF_HOSTED_FN("resolveExport", "ModuleResolveExport", 3, 0),
+ JS_SELF_HOSTED_FN("declarationInstantiation", "ModuleDeclarationInstantiation", 0, 0),
+ JS_SELF_HOSTED_FN("evaluation", "ModuleEvaluation", 0, 0),
+ JS_FS_END
+ };
+
+ RootedObject proto(cx, global->createBlankPrototype<PlainObject>(cx));
+ if (!proto)
+ return false;
+
+ if (!DefinePropertiesAndFunctions(cx, proto, protoAccessors, protoFunctions))
+ return false;
+
+ global->setReservedSlot(MODULE_PROTO, ObjectValue(*proto));
+ return true;
+}
+
+#undef DEFINE_GETTER_FUNCTIONS
+#undef DEFINE_STRING_ACCESSOR_METHOD
+#undef DEFINE_ARRAY_SLOT_ACCESSOR
+
+///////////////////////////////////////////////////////////////////////////
+// ModuleBuilder
+
+ModuleBuilder::ModuleBuilder(ExclusiveContext* cx, HandleModuleObject module)
+ : cx_(cx),
+ module_(cx, module),
+ requestedModules_(cx, AtomVector(cx)),
+ importedBoundNames_(cx, AtomVector(cx)),
+ importEntries_(cx, ImportEntryVector(cx)),
+ exportEntries_(cx, ExportEntryVector(cx)),
+ localExportEntries_(cx, ExportEntryVector(cx)),
+ indirectExportEntries_(cx, ExportEntryVector(cx)),
+ starExportEntries_(cx, ExportEntryVector(cx))
+{}
+
+bool
+ModuleBuilder::buildTables()
+{
+ for (const auto& e : exportEntries_) {
+ RootedExportEntryObject exp(cx_, e);
+ if (!exp->moduleRequest()) {
+ RootedImportEntryObject importEntry(cx_, importEntryFor(exp->localName()));
+ if (!importEntry) {
+ if (!localExportEntries_.append(exp))
+ return false;
+ } else {
+ if (importEntry->importName() == cx_->names().star) {
+ if (!localExportEntries_.append(exp))
+ return false;
+ } else {
+ RootedAtom exportName(cx_, exp->exportName());
+ RootedAtom moduleRequest(cx_, importEntry->moduleRequest());
+ RootedAtom importName(cx_, importEntry->importName());
+ RootedExportEntryObject exportEntry(cx_);
+ exportEntry = ExportEntryObject::create(cx_,
+ exportName,
+ moduleRequest,
+ importName,
+ nullptr);
+ if (!exportEntry || !indirectExportEntries_.append(exportEntry))
+ return false;
+ }
+ }
+ } else if (exp->importName() == cx_->names().star) {
+ if (!starExportEntries_.append(exp))
+ return false;
+ } else {
+ if (!indirectExportEntries_.append(exp))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+ModuleBuilder::initModule()
+{
+ RootedArrayObject requestedModules(cx_, createArray<JSAtom*>(requestedModules_));
+ if (!requestedModules)
+ return false;
+
+ RootedArrayObject importEntries(cx_, createArray<ImportEntryObject*>(importEntries_));
+ if (!importEntries)
+ return false;
+
+ RootedArrayObject localExportEntries(cx_, createArray<ExportEntryObject*>(localExportEntries_));
+ if (!localExportEntries)
+ return false;
+
+ RootedArrayObject indirectExportEntries(cx_);
+ indirectExportEntries = createArray<ExportEntryObject*>(indirectExportEntries_);
+ if (!indirectExportEntries)
+ return false;
+
+ RootedArrayObject starExportEntries(cx_, createArray<ExportEntryObject*>(starExportEntries_));
+ if (!starExportEntries)
+ return false;
+
+ module_->initImportExportData(requestedModules,
+ importEntries,
+ localExportEntries,
+ indirectExportEntries,
+ starExportEntries);
+
+ return true;
+}
+
+bool
+ModuleBuilder::processImport(frontend::ParseNode* pn)
+{
+ MOZ_ASSERT(pn->isKind(PNK_IMPORT));
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ MOZ_ASSERT(pn->pn_left->isKind(PNK_IMPORT_SPEC_LIST));
+ MOZ_ASSERT(pn->pn_right->isKind(PNK_STRING));
+
+ RootedAtom module(cx_, pn->pn_right->pn_atom);
+ if (!maybeAppendRequestedModule(module))
+ return false;
+
+ for (ParseNode* spec = pn->pn_left->pn_head; spec; spec = spec->pn_next) {
+ MOZ_ASSERT(spec->isKind(PNK_IMPORT_SPEC));
+ MOZ_ASSERT(spec->pn_left->isArity(PN_NAME));
+ MOZ_ASSERT(spec->pn_right->isArity(PN_NAME));
+
+ RootedAtom importName(cx_, spec->pn_left->pn_atom);
+ RootedAtom localName(cx_, spec->pn_right->pn_atom);
+
+ if (!importedBoundNames_.append(localName))
+ return false;
+
+ RootedImportEntryObject importEntry(cx_);
+ importEntry = ImportEntryObject::create(cx_, module, importName, localName);
+ if (!importEntry || !importEntries_.append(importEntry))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+ModuleBuilder::processExport(frontend::ParseNode* pn)
+{
+ MOZ_ASSERT(pn->isKind(PNK_EXPORT) || pn->isKind(PNK_EXPORT_DEFAULT));
+ MOZ_ASSERT(pn->getArity() == pn->isKind(PNK_EXPORT) ? PN_UNARY : PN_BINARY);
+
+ bool isDefault = pn->getKind() == PNK_EXPORT_DEFAULT;
+ ParseNode* kid = isDefault ? pn->pn_left : pn->pn_kid;
+
+ switch (kid->getKind()) {
+ case PNK_EXPORT_SPEC_LIST:
+ MOZ_ASSERT(!isDefault);
+ for (ParseNode* spec = kid->pn_head; spec; spec = spec->pn_next) {
+ MOZ_ASSERT(spec->isKind(PNK_EXPORT_SPEC));
+ RootedAtom localName(cx_, spec->pn_left->pn_atom);
+ RootedAtom exportName(cx_, spec->pn_right->pn_atom);
+ if (!appendExportEntry(exportName, localName))
+ return false;
+ }
+ break;
+
+ case PNK_CLASS: {
+ const ClassNode& cls = kid->as<ClassNode>();
+ MOZ_ASSERT(cls.names());
+ RootedAtom localName(cx_, cls.names()->innerBinding()->pn_atom);
+ RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get());
+ if (!appendExportEntry(exportName, localName))
+ return false;
+ break;
+ }
+
+ case PNK_VAR:
+ case PNK_CONST:
+ case PNK_LET: {
+ MOZ_ASSERT(kid->isArity(PN_LIST));
+ for (ParseNode* var = kid->pn_head; var; var = var->pn_next) {
+ if (var->isKind(PNK_ASSIGN))
+ var = var->pn_left;
+ MOZ_ASSERT(var->isKind(PNK_NAME));
+ RootedAtom localName(cx_, var->pn_atom);
+ RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get());
+ if (!appendExportEntry(exportName, localName))
+ return false;
+ }
+ break;
+ }
+
+ case PNK_FUNCTION: {
+ RootedFunction func(cx_, kid->pn_funbox->function());
+ if (!func->isArrow()) {
+ RootedAtom localName(cx_, func->name());
+ RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get());
+ MOZ_ASSERT_IF(isDefault, localName);
+ if (!appendExportEntry(exportName, localName))
+ return false;
+ break;
+ }
+ }
+
+ MOZ_FALLTHROUGH; // Arrow functions are handled below.
+
+ default:
+ MOZ_ASSERT(isDefault);
+ RootedAtom localName(cx_, cx_->names().starDefaultStar);
+ RootedAtom exportName(cx_, cx_->names().default_);
+ if (!appendExportEntry(exportName, localName))
+ return false;
+ break;
+ }
+ return true;
+}
+
+bool
+ModuleBuilder::processExportFrom(frontend::ParseNode* pn)
+{
+ MOZ_ASSERT(pn->isKind(PNK_EXPORT_FROM));
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ MOZ_ASSERT(pn->pn_left->isKind(PNK_EXPORT_SPEC_LIST));
+ MOZ_ASSERT(pn->pn_right->isKind(PNK_STRING));
+
+ RootedAtom module(cx_, pn->pn_right->pn_atom);
+ if (!maybeAppendRequestedModule(module))
+ return false;
+
+ for (ParseNode* spec = pn->pn_left->pn_head; spec; spec = spec->pn_next) {
+ if (spec->isKind(PNK_EXPORT_SPEC)) {
+ RootedAtom bindingName(cx_, spec->pn_left->pn_atom);
+ RootedAtom exportName(cx_, spec->pn_right->pn_atom);
+ if (!appendExportFromEntry(exportName, module, bindingName))
+ return false;
+ } else {
+ MOZ_ASSERT(spec->isKind(PNK_EXPORT_BATCH_SPEC));
+ RootedAtom importName(cx_, cx_->names().star);
+ if (!appendExportFromEntry(nullptr, module, importName))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+ImportEntryObject*
+ModuleBuilder::importEntryFor(JSAtom* localName) const
+{
+ for (auto import : importEntries_) {
+ if (import->localName() == localName)
+ return import;
+ }
+ return nullptr;
+}
+
+bool
+ModuleBuilder::hasExportedName(JSAtom* name) const
+{
+ for (auto entry : exportEntries_) {
+ if (entry->exportName() == name)
+ return true;
+ }
+ return false;
+}
+
+bool
+ModuleBuilder::appendExportEntry(HandleAtom exportName, HandleAtom localName)
+{
+ Rooted<ExportEntryObject*> exportEntry(cx_);
+ exportEntry = ExportEntryObject::create(cx_, exportName, nullptr, nullptr, localName);
+ return exportEntry && exportEntries_.append(exportEntry);
+}
+
+bool
+ModuleBuilder::appendExportFromEntry(HandleAtom exportName, HandleAtom moduleRequest,
+ HandleAtom importName)
+{
+ Rooted<ExportEntryObject*> exportEntry(cx_);
+ exportEntry = ExportEntryObject::create(cx_, exportName, moduleRequest, importName, nullptr);
+ return exportEntry && exportEntries_.append(exportEntry);
+}
+
+bool
+ModuleBuilder::maybeAppendRequestedModule(HandleAtom module)
+{
+ for (auto m : requestedModules_) {
+ if (m == module)
+ return true;
+ }
+ return requestedModules_.append(module);
+}
+
+static Value
+MakeElementValue(JSString *string)
+{
+ return StringValue(string);
+}
+
+static Value
+MakeElementValue(JSObject *object)
+{
+ return ObjectValue(*object);
+}
+
+template <typename T>
+ArrayObject* ModuleBuilder::createArray(const GCVector<T>& vector)
+{
+ uint32_t length = vector.length();
+ RootedArrayObject array(cx_, NewDenseFullyAllocatedArray(cx_, length));
+ if (!array)
+ return nullptr;
+
+ array->setDenseInitializedLength(length);
+ for (uint32_t i = 0; i < length; i++)
+ array->initDenseElement(i, MakeElementValue(vector[i]));
+
+ return array;
+}
diff --git a/js/src/builtin/ModuleObject.h b/js/src/builtin/ModuleObject.h
new file mode 100644
index 000000000..d0ed8ed08
--- /dev/null
+++ b/js/src/builtin/ModuleObject.h
@@ -0,0 +1,358 @@
+/* -*- 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/. */
+
+#ifndef builtin_ModuleObject_h
+#define builtin_ModuleObject_h
+
+#include "jsapi.h"
+#include "jsatom.h"
+
+#include "builtin/SelfHostingDefines.h"
+#include "gc/Zone.h"
+#include "js/GCVector.h"
+#include "js/Id.h"
+#include "vm/NativeObject.h"
+#include "vm/ProxyObject.h"
+
+namespace js {
+
+class ModuleEnvironmentObject;
+class ModuleObject;
+
+namespace frontend {
+class ParseNode;
+} /* namespace frontend */
+
+typedef Rooted<ModuleObject*> RootedModuleObject;
+typedef Handle<ModuleObject*> HandleModuleObject;
+typedef Rooted<ModuleEnvironmentObject*> RootedModuleEnvironmentObject;
+typedef Handle<ModuleEnvironmentObject*> HandleModuleEnvironmentObject;
+
+class ImportEntryObject : public NativeObject
+{
+ public:
+ enum
+ {
+ ModuleRequestSlot = 0,
+ ImportNameSlot,
+ LocalNameSlot,
+ SlotCount
+ };
+
+ static const Class class_;
+ static JSObject* initClass(ExclusiveContext* cx, HandleObject obj);
+ static bool isInstance(HandleValue value);
+ static ImportEntryObject* create(ExclusiveContext* cx,
+ HandleAtom moduleRequest,
+ HandleAtom importName,
+ HandleAtom localName);
+ JSAtom* moduleRequest() const;
+ JSAtom* importName() const;
+ JSAtom* localName() const;
+};
+
+typedef Rooted<ImportEntryObject*> RootedImportEntryObject;
+typedef Handle<ImportEntryObject*> HandleImportEntryObject;
+
+class ExportEntryObject : public NativeObject
+{
+ public:
+ enum
+ {
+ ExportNameSlot = 0,
+ ModuleRequestSlot,
+ ImportNameSlot,
+ LocalNameSlot,
+ SlotCount
+ };
+
+ static const Class class_;
+ static JSObject* initClass(ExclusiveContext* cx, HandleObject obj);
+ static bool isInstance(HandleValue value);
+ static ExportEntryObject* create(ExclusiveContext* cx,
+ HandleAtom maybeExportName,
+ HandleAtom maybeModuleRequest,
+ HandleAtom maybeImportName,
+ HandleAtom maybeLocalName);
+ JSAtom* exportName() const;
+ JSAtom* moduleRequest() const;
+ JSAtom* importName() const;
+ JSAtom* localName() const;
+};
+
+typedef Rooted<ExportEntryObject*> RootedExportEntryObject;
+typedef Handle<ExportEntryObject*> HandleExportEntryObject;
+
+class IndirectBindingMap
+{
+ public:
+ explicit IndirectBindingMap(Zone* zone);
+ bool init();
+
+ void trace(JSTracer* trc);
+
+ bool putNew(JSContext* cx, HandleId name,
+ HandleModuleEnvironmentObject environment, HandleId localName);
+
+ size_t count() const {
+ return map_.count();
+ }
+
+ bool has(jsid name) const {
+ return map_.has(name);
+ }
+
+ bool lookup(jsid name, ModuleEnvironmentObject** envOut, Shape** shapeOut) const;
+
+ template <typename Func>
+ void forEachExportedName(Func func) const {
+ for (auto r = map_.all(); !r.empty(); r.popFront())
+ func(r.front().key());
+ }
+
+ private:
+ struct Binding
+ {
+ Binding(ModuleEnvironmentObject* environment, Shape* shape);
+ HeapPtr<ModuleEnvironmentObject*> environment;
+ HeapPtr<Shape*> shape;
+ };
+
+ typedef HashMap<jsid, Binding, DefaultHasher<jsid>, ZoneAllocPolicy> Map;
+
+ Map map_;
+};
+
+class ModuleNamespaceObject : public ProxyObject
+{
+ public:
+ static bool isInstance(HandleValue value);
+ static ModuleNamespaceObject* create(JSContext* cx, HandleModuleObject module);
+
+ ModuleObject& module();
+ JSObject& exports();
+ IndirectBindingMap& bindings();
+
+ bool addBinding(JSContext* cx, HandleAtom exportedName, HandleModuleObject targetModule,
+ HandleAtom localName);
+
+ private:
+ struct ProxyHandler : public BaseProxyHandler
+ {
+ enum
+ {
+ EnumerateFunctionSlot = 0
+ };
+
+ ProxyHandler();
+
+ JS::Value getEnumerateFunction(HandleObject proxy) const;
+
+ bool getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const override;
+ bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const override;
+ bool ownPropertyKeys(JSContext* cx, HandleObject proxy,
+ AutoIdVector& props) const override;
+ bool delete_(JSContext* cx, HandleObject proxy, HandleId id,
+ ObjectOpResult& result) const override;
+ bool getPrototype(JSContext* cx, HandleObject proxy,
+ MutableHandleObject protop) const override;
+ bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
+ ObjectOpResult& result) const override;
+ bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
+ MutableHandleObject protop) const override;
+ bool setImmutablePrototype(JSContext* cx, HandleObject proxy,
+ bool* succeeded) const override;
+
+ bool preventExtensions(JSContext* cx, HandleObject proxy,
+ ObjectOpResult& result) const override;
+ bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const override;
+ bool has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const override;
+ bool get(JSContext* cx, HandleObject proxy, HandleValue receiver,
+ HandleId id, MutableHandleValue vp) const override;
+ bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result) const override;
+
+ static const char family;
+ };
+
+ public:
+ static const ProxyHandler proxyHandler;
+};
+
+typedef Rooted<ModuleNamespaceObject*> RootedModuleNamespaceObject;
+typedef Handle<ModuleNamespaceObject*> HandleModuleNamespaceObject;
+
+struct FunctionDeclaration
+{
+ FunctionDeclaration(HandleAtom name, HandleFunction fun);
+ void trace(JSTracer* trc);
+
+ HeapPtr<JSAtom*> name;
+ HeapPtr<JSFunction*> fun;
+};
+
+using FunctionDeclarationVector = GCVector<FunctionDeclaration, 0, ZoneAllocPolicy>;
+
+// Possible values for ModuleState are defined in SelfHostingDefines.h.
+using ModuleState = int32_t;
+
+class ModuleObject : public NativeObject
+{
+ public:
+ enum
+ {
+ ScriptSlot = 0,
+ InitialEnvironmentSlot,
+ EnvironmentSlot,
+ NamespaceSlot,
+ StateSlot,
+ HostDefinedSlot,
+ RequestedModulesSlot,
+ ImportEntriesSlot,
+ LocalExportEntriesSlot,
+ IndirectExportEntriesSlot,
+ StarExportEntriesSlot,
+ ImportBindingsSlot,
+ NamespaceExportsSlot,
+ NamespaceBindingsSlot,
+ FunctionDeclarationsSlot,
+ SlotCount
+ };
+
+ static_assert(EnvironmentSlot == MODULE_OBJECT_ENVIRONMENT_SLOT,
+ "EnvironmentSlot must match self-hosting define");
+
+ static const Class class_;
+
+ static bool isInstance(HandleValue value);
+
+ static ModuleObject* create(ExclusiveContext* cx);
+ void init(HandleScript script);
+ void setInitialEnvironment(Handle<ModuleEnvironmentObject*> initialEnvironment);
+ void initImportExportData(HandleArrayObject requestedModules,
+ HandleArrayObject importEntries,
+ HandleArrayObject localExportEntries,
+ HandleArrayObject indiretExportEntries,
+ HandleArrayObject starExportEntries);
+ static bool Freeze(JSContext* cx, HandleModuleObject self);
+#ifdef DEBUG
+ static bool IsFrozen(JSContext* cx, HandleModuleObject self);
+#endif
+ void fixEnvironmentsAfterCompartmentMerge(JSContext* cx);
+
+ JSScript* script() const;
+ Scope* enclosingScope() const;
+ ModuleEnvironmentObject& initialEnvironment() const;
+ ModuleEnvironmentObject* environment() const;
+ ModuleNamespaceObject* namespace_();
+ ModuleState state() const;
+ Value hostDefinedField() const;
+ ArrayObject& requestedModules() const;
+ ArrayObject& importEntries() const;
+ ArrayObject& localExportEntries() const;
+ ArrayObject& indirectExportEntries() const;
+ ArrayObject& starExportEntries() const;
+ IndirectBindingMap& importBindings();
+ JSObject* namespaceExports();
+ IndirectBindingMap* namespaceBindings();
+
+ static bool DeclarationInstantiation(JSContext* cx, HandleModuleObject self);
+ static bool Evaluation(JSContext* cx, HandleModuleObject self);
+
+ void setHostDefinedField(const JS::Value& value);
+
+ // For intrinsic_CreateModuleEnvironment.
+ void createEnvironment();
+
+ // For BytecodeEmitter.
+ bool noteFunctionDeclaration(ExclusiveContext* cx, HandleAtom name, HandleFunction fun);
+
+ // For intrinsic_InstantiateModuleFunctionDeclarations.
+ static bool instantiateFunctionDeclarations(JSContext* cx, HandleModuleObject self);
+
+ void setState(ModuleState newState);
+
+ // For intrinsic_EvaluateModule.
+ static bool evaluate(JSContext* cx, HandleModuleObject self, MutableHandleValue rval);
+
+ // For intrinsic_NewModuleNamespace.
+ static ModuleNamespaceObject* createNamespace(JSContext* cx, HandleModuleObject self,
+ HandleObject exports);
+
+ private:
+ static const ClassOps classOps_;
+
+ static void trace(JSTracer* trc, JSObject* obj);
+ static void finalize(js::FreeOp* fop, JSObject* obj);
+
+ bool hasScript() const;
+ bool hasImportBindings() const;
+ FunctionDeclarationVector* functionDeclarations();
+};
+
+// Process a module's parse tree to collate the import and export data used when
+// creating a ModuleObject.
+class MOZ_STACK_CLASS ModuleBuilder
+{
+ public:
+ explicit ModuleBuilder(ExclusiveContext* cx, HandleModuleObject module);
+
+ bool processImport(frontend::ParseNode* pn);
+ bool processExport(frontend::ParseNode* pn);
+ bool processExportFrom(frontend::ParseNode* pn);
+
+ bool hasExportedName(JSAtom* name) const;
+
+ using ExportEntryVector = GCVector<ExportEntryObject*>;
+ const ExportEntryVector& localExportEntries() const {
+ return localExportEntries_;
+ }
+
+ bool buildTables();
+ bool initModule();
+
+ private:
+ using AtomVector = GCVector<JSAtom*>;
+ using RootedAtomVector = JS::Rooted<AtomVector>;
+ using ImportEntryVector = GCVector<ImportEntryObject*>;
+ using RootedImportEntryVector = JS::Rooted<ImportEntryVector>;
+ using RootedExportEntryVector = JS::Rooted<ExportEntryVector>;
+
+ ExclusiveContext* cx_;
+ RootedModuleObject module_;
+ RootedAtomVector requestedModules_;
+ RootedAtomVector importedBoundNames_;
+ RootedImportEntryVector importEntries_;
+ RootedExportEntryVector exportEntries_;
+ RootedExportEntryVector localExportEntries_;
+ RootedExportEntryVector indirectExportEntries_;
+ RootedExportEntryVector starExportEntries_;
+
+ ImportEntryObject* importEntryFor(JSAtom* localName) const;
+
+ bool appendExportEntry(HandleAtom exportName, HandleAtom localName);
+ bool appendExportFromEntry(HandleAtom exportName, HandleAtom moduleRequest,
+ HandleAtom importName);
+
+ bool maybeAppendRequestedModule(HandleAtom module);
+
+ template <typename T>
+ ArrayObject* createArray(const GCVector<T>& vector);
+};
+
+} // namespace js
+
+template<>
+inline bool
+JSObject::is<js::ModuleNamespaceObject>() const
+{
+ return js::IsDerivedProxyObject(this, &js::ModuleNamespaceObject::proxyHandler);
+}
+
+#endif /* builtin_ModuleObject_h */
diff --git a/js/src/builtin/Number.js b/js/src/builtin/Number.js
new file mode 100644
index 000000000..07b2be57a
--- /dev/null
+++ b/js/src/builtin/Number.js
@@ -0,0 +1,87 @@
+/* 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/. */
+
+/*global intl_NumberFormat: false, */
+
+
+var numberFormatCache = new Record();
+
+
+/**
+ * Format this Number object into a string, using the locale and formatting options
+ * provided.
+ *
+ * Spec: ECMAScript Language Specification, 5.1 edition, 15.7.4.3.
+ * Spec: ECMAScript Internationalization API Specification, 13.2.1.
+ */
+function Number_toLocaleString() {
+ // Steps 1-2. Note that valueOf enforces "this Number value" restrictions.
+ var x = callFunction(std_Number_valueOf, this);
+
+ // Steps 2-3.
+ var locales = arguments.length > 0 ? arguments[0] : undefined;
+ var options = arguments.length > 1 ? arguments[1] : undefined;
+
+ // Step 4.
+ var numberFormat;
+ if (locales === undefined && options === undefined) {
+ // This cache only optimizes for the old ES5 toLocaleString without
+ // locales and options.
+ if (numberFormatCache.numberFormat === undefined)
+ numberFormatCache.numberFormat = intl_NumberFormat(locales, options);
+ numberFormat = numberFormatCache.numberFormat;
+ } else {
+ numberFormat = intl_NumberFormat(locales, options);
+ }
+
+ // Step 5.
+ return intl_FormatNumber(numberFormat, x);
+}
+
+// ES6 draft ES6 20.1.2.4
+function Number_isFinite(num) {
+ if (typeof num !== "number")
+ return false;
+ return num - num === 0;
+}
+
+// ES6 draft ES6 20.1.2.2
+function Number_isNaN(num) {
+ if (typeof num !== "number")
+ return false;
+ return num !== num;
+}
+
+// ES6 draft ES6 20.1.2.5
+function Number_isSafeInteger(number) {
+ // Step 1.
+ if (typeof number !== 'number')
+ return false;
+
+ // Step 2.
+ if (!Number_isFinite(number))
+ return false;
+
+ // Step 3.
+ var integer = ToInteger(number);
+
+ // Step 4.
+ if (integer !== number)
+ return false;
+
+ // Step 5. If abs(integer) <= 2**53 - 1, return true.
+ if (std_Math_abs(integer) <= 9007199254740991)
+ return true;
+
+ // Step 6.
+ return false;
+}
+
+function Global_isNaN(number) {
+ return Number_isNaN(ToNumber(number));
+}
+
+function Global_isFinite(number){
+ return Number_isFinite(ToNumber(number));
+}
diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp
new file mode 100644
index 000000000..cd4ac122c
--- /dev/null
+++ b/js/src/builtin/Object.cpp
@@ -0,0 +1,1341 @@
+/* -*- 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 "builtin/Object.h"
+
+#include "mozilla/ArrayUtils.h"
+
+#include "jscntxt.h"
+
+#include "builtin/Eval.h"
+#include "frontend/BytecodeCompiler.h"
+#include "jit/InlinableNatives.h"
+#include "js/UniquePtr.h"
+#include "vm/StringBuffer.h"
+
+#include "jsobjinlines.h"
+
+#include "vm/NativeObject-inl.h"
+#include "vm/Shape-inl.h"
+
+using namespace js;
+
+using js::frontend::IsIdentifier;
+using mozilla::ArrayLength;
+
+bool
+js::obj_construct(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject obj(cx, nullptr);
+ if (args.isConstructing() && (&args.newTarget().toObject() != &args.callee())) {
+ RootedObject newTarget(cx, &args.newTarget().toObject());
+ obj = CreateThis(cx, &PlainObject::class_, newTarget);
+ if (!obj)
+ return false;
+ } else if (args.length() > 0 && !args[0].isNullOrUndefined()) {
+ obj = ToObject(cx, args[0]);
+ if (!obj)
+ return false;
+ } else {
+ /* Make an object whether this was called with 'new' or not. */
+ if (!NewObjectScriptedCall(cx, &obj))
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/* ES5 15.2.4.7. */
+bool
+js::obj_propertyIsEnumerable(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ HandleValue idValue = args.get(0);
+
+ // As an optimization, provide a fast path when rooting is not necessary and
+ // we can safely retrieve the attributes from the object's shape.
+
+ /* Steps 1-2. */
+ jsid id;
+ if (args.thisv().isObject() && ValueToId<NoGC>(cx, idValue, &id)) {
+ JSObject* obj = &args.thisv().toObject();
+
+ /* Step 3. */
+ Shape* shape;
+ if (obj->isNative() &&
+ NativeLookupOwnProperty<NoGC>(cx, &obj->as<NativeObject>(), id, &shape))
+ {
+ /* Step 4. */
+ if (!shape) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ /* Step 5. */
+ unsigned attrs = GetShapeAttributes(obj, shape);
+ args.rval().setBoolean((attrs & JSPROP_ENUMERATE) != 0);
+ return true;
+ }
+ }
+
+ /* Step 1. */
+ RootedId idRoot(cx);
+ if (!ToPropertyKey(cx, idValue, &idRoot))
+ return false;
+
+ /* Step 2. */
+ RootedObject obj(cx, ToObject(cx, args.thisv()));
+ if (!obj)
+ return false;
+
+ /* Step 3. */
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!GetOwnPropertyDescriptor(cx, obj, idRoot, &desc))
+ return false;
+
+ /* Steps 4-5. */
+ args.rval().setBoolean(desc.object() && desc.enumerable());
+ return true;
+}
+
+#if JS_HAS_TOSOURCE
+static bool
+obj_toSource(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ JS_CHECK_RECURSION(cx, return false);
+
+ RootedObject obj(cx, ToObject(cx, args.thisv()));
+ if (!obj)
+ return false;
+
+ JSString* str = ObjectToSource(cx, obj);
+ if (!str)
+ return false;
+
+ args.rval().setString(str);
+ return true;
+}
+
+/*
+ * Given a function source string, return the offset and length of the part
+ * between '(function $name' and ')'.
+ */
+template <typename CharT>
+static bool
+ArgsAndBodySubstring(mozilla::Range<const CharT> chars, size_t* outOffset, size_t* outLen)
+{
+ const CharT* const start = chars.begin().get();
+ const CharT* const end = chars.end().get();
+ const CharT* s = start;
+
+ uint8_t parenChomp = 0;
+ if (s[0] == '(') {
+ s++;
+ parenChomp = 1;
+ }
+
+ /* Try to jump "function" keyword. */
+ s = js_strchr_limit(s, ' ', end);
+ if (!s)
+ return false;
+
+ /*
+ * Jump over the function's name: it can't be encoded as part
+ * of an ECMA getter or setter.
+ */
+ s = js_strchr_limit(s, '(', end);
+ if (!s)
+ return false;
+
+ if (*s == ' ')
+ s++;
+
+ *outOffset = s - start;
+ *outLen = end - s - parenChomp;
+ MOZ_ASSERT(*outOffset + *outLen <= chars.length());
+ return true;
+}
+
+JSString*
+js::ObjectToSource(JSContext* cx, HandleObject obj)
+{
+ /* If outermost, we need parentheses to be an expression, not a block. */
+ bool outermost = (cx->cycleDetectorSet.count() == 0);
+
+ AutoCycleDetector detector(cx, obj);
+ if (!detector.init())
+ return nullptr;
+ if (detector.foundCycle())
+ return NewStringCopyZ<CanGC>(cx, "{}");
+
+ StringBuffer buf(cx);
+ if (outermost && !buf.append('('))
+ return nullptr;
+ if (!buf.append('{'))
+ return nullptr;
+
+ RootedValue v0(cx), v1(cx);
+ MutableHandleValue val[2] = {&v0, &v1};
+
+ RootedString str0(cx), str1(cx);
+ MutableHandleString gsop[2] = {&str0, &str1};
+
+ AutoIdVector idv(cx);
+ if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_SYMBOLS, &idv))
+ return nullptr;
+
+ bool comma = false;
+ for (size_t i = 0; i < idv.length(); ++i) {
+ RootedId id(cx, idv[i]);
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
+ return nullptr;
+
+ int valcnt = 0;
+ if (desc.object()) {
+ if (desc.isAccessorDescriptor()) {
+ if (desc.hasGetterObject() && desc.getterObject()) {
+ val[valcnt].setObject(*desc.getterObject());
+ gsop[valcnt].set(cx->names().get);
+ valcnt++;
+ }
+ if (desc.hasSetterObject() && desc.setterObject()) {
+ val[valcnt].setObject(*desc.setterObject());
+ gsop[valcnt].set(cx->names().set);
+ valcnt++;
+ }
+ } else {
+ valcnt = 1;
+ val[0].set(desc.value());
+ gsop[0].set(nullptr);
+ }
+ }
+
+ /* Convert id to a string. */
+ RootedString idstr(cx);
+ if (JSID_IS_SYMBOL(id)) {
+ RootedValue v(cx, SymbolValue(JSID_TO_SYMBOL(id)));
+ idstr = ValueToSource(cx, v);
+ if (!idstr)
+ return nullptr;
+ } else {
+ RootedValue idv(cx, IdToValue(id));
+ idstr = ToString<CanGC>(cx, idv);
+ if (!idstr)
+ return nullptr;
+
+ /*
+ * If id is a string that's not an identifier, or if it's a negative
+ * integer, then it must be quoted.
+ */
+ if (JSID_IS_ATOM(id)
+ ? !IsIdentifier(JSID_TO_ATOM(id))
+ : JSID_TO_INT(id) < 0)
+ {
+ idstr = QuoteString(cx, idstr, char16_t('\''));
+ if (!idstr)
+ return nullptr;
+ }
+ }
+
+ for (int j = 0; j < valcnt; j++) {
+ /* Convert val[j] to its canonical source form. */
+ JSString* valsource = ValueToSource(cx, val[j]);
+ if (!valsource)
+ return nullptr;
+
+ RootedLinearString valstr(cx, valsource->ensureLinear(cx));
+ if (!valstr)
+ return nullptr;
+
+ size_t voffset = 0;
+ size_t vlength = valstr->length();
+
+ /*
+ * Remove '(function ' from the beginning of valstr and ')' from the
+ * end so that we can put "get" in front of the function definition.
+ */
+ if (gsop[j] && IsFunctionObject(val[j])) {
+ bool success;
+ JS::AutoCheckCannotGC nogc;
+ if (valstr->hasLatin1Chars())
+ success = ArgsAndBodySubstring(valstr->latin1Range(nogc), &voffset, &vlength);
+ else
+ success = ArgsAndBodySubstring(valstr->twoByteRange(nogc), &voffset, &vlength);
+ if (!success)
+ gsop[j].set(nullptr);
+ }
+
+ if (comma && !buf.append(", "))
+ return nullptr;
+ comma = true;
+
+ if (gsop[j]) {
+ if (!buf.append(gsop[j]) || !buf.append(' '))
+ return nullptr;
+ }
+ if (JSID_IS_SYMBOL(id) && !buf.append('['))
+ return nullptr;
+ if (!buf.append(idstr))
+ return nullptr;
+ if (JSID_IS_SYMBOL(id) && !buf.append(']'))
+ return nullptr;
+ if (!buf.append(gsop[j] ? ' ' : ':'))
+ return nullptr;
+
+ if (!buf.appendSubstring(valstr, voffset, vlength))
+ return nullptr;
+ }
+ }
+
+ if (!buf.append('}'))
+ return nullptr;
+ if (outermost && !buf.append(')'))
+ return nullptr;
+
+ return buf.finishString();
+}
+#endif /* JS_HAS_TOSOURCE */
+
+// ES6 19.1.3.6
+bool
+js::obj_toString(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (args.thisv().isUndefined()) {
+ args.rval().setString(cx->names().objectUndefined);
+ return true;
+ }
+
+ // Step 2.
+ if (args.thisv().isNull()) {
+ args.rval().setString(cx->names().objectNull);
+ return true;
+ }
+
+ // Step 3.
+ RootedObject obj(cx, ToObject(cx, args.thisv()));
+ if (!obj)
+ return false;
+
+ // Step 4.
+ bool isArray;
+ if (!IsArray(cx, obj, &isArray))
+ return false;
+
+ // Step 5.
+ RootedString builtinTag(cx);
+ if (isArray) {
+ builtinTag = cx->names().objectArray;
+ } else {
+ // Steps 6-13.
+ ESClass cls;
+ if (!GetBuiltinClass(cx, obj, &cls))
+ return false;
+
+ switch (cls) {
+ case ESClass::String:
+ builtinTag = cx->names().objectString;
+ break;
+ case ESClass::Arguments:
+ builtinTag = cx->names().objectArguments;
+ break;
+ case ESClass::Error:
+ builtinTag = cx->names().objectError;
+ break;
+ case ESClass::Boolean:
+ builtinTag = cx->names().objectBoolean;
+ break;
+ case ESClass::Number:
+ builtinTag = cx->names().objectNumber;
+ break;
+ case ESClass::Date:
+ builtinTag = cx->names().objectDate;
+ break;
+ case ESClass::RegExp:
+ builtinTag = cx->names().objectRegExp;
+ break;
+ default:
+ if (obj->isCallable()) {
+ // Non-standard: Prevent <object> from showing up as Function.
+ RootedObject unwrapped(cx, CheckedUnwrap(obj));
+ if (!unwrapped || !unwrapped->getClass()->isDOMClass())
+ builtinTag = cx->names().objectFunction;
+ }
+ break;
+ }
+ }
+ // Step 14.
+ // Currently omitted for non-standard fallback.
+
+ // Step 15.
+ RootedValue tag(cx);
+ RootedId toStringTagId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().toStringTag));
+ if (!GetProperty(cx, obj, obj, toStringTagId, &tag))
+ return false;
+
+ // Step 16.
+ if (!tag.isString()) {
+ // Non-standard (bug 1277801): Use ClassName as a fallback in the interim
+ if (!builtinTag) {
+ const char* className = GetObjectClassName(cx, obj);
+
+ StringBuffer sb(cx);
+ if (!sb.append("[object ") || !sb.append(className, strlen(className)) ||
+ !sb.append("]"))
+ {
+ return false;
+ }
+
+ builtinTag = sb.finishString();
+ if (!builtinTag)
+ return false;
+ }
+
+ args.rval().setString(builtinTag);
+ return true;
+ }
+
+ // Step 17.
+ StringBuffer sb(cx);
+ if (!sb.append("[object ") || !sb.append(tag.toString()) || !sb.append("]"))
+ return false;
+
+ RootedString str(cx, sb.finishString());
+ if (!str)
+ return false;
+
+ args.rval().setString(str);
+ return true;
+}
+
+
+bool
+js::obj_valueOf(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject obj(cx, ToObject(cx, args.thisv()));
+ if (!obj)
+ return false;
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool
+obj_setPrototypeOf(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 2) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+ "Object.setPrototypeOf", "1", "");
+ return false;
+ }
+
+ /* Step 1-2. */
+ if (args[0].isNullOrUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
+ args[0].isNull() ? "null" : "undefined", "object");
+ return false;
+ }
+
+ /* Step 3. */
+ if (!args[1].isObjectOrNull()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
+ "Object.setPrototypeOf", "an object or null",
+ InformalValueTypeName(args[1]));
+ return false;
+ }
+
+ /* Step 4. */
+ if (!args[0].isObject()) {
+ args.rval().set(args[0]);
+ return true;
+ }
+
+ /* Step 5-7. */
+ RootedObject obj(cx, &args[0].toObject());
+ RootedObject newProto(cx, args[1].toObjectOrNull());
+ if (!SetPrototype(cx, obj, newProto))
+ return false;
+
+ /* Step 8. */
+ args.rval().set(args[0]);
+ return true;
+}
+
+#if JS_HAS_OBJ_WATCHPOINT
+
+bool
+js::WatchHandler(JSContext* cx, JSObject* obj_, jsid id_, const JS::Value& old,
+ JS::Value* nvp, void* closure)
+{
+ RootedObject obj(cx, obj_);
+ RootedId id(cx, id_);
+
+ /* Avoid recursion on (obj, id) already being watched on cx. */
+ AutoResolving resolving(cx, obj, id, AutoResolving::WATCH);
+ if (resolving.alreadyStarted())
+ return true;
+
+ FixedInvokeArgs<3> args(cx);
+
+ args[0].set(IdToValue(id));
+ args[1].set(old);
+ args[2].set(*nvp);
+
+ RootedValue callable(cx, ObjectValue(*static_cast<JSObject*>(closure)));
+ RootedValue thisv(cx, ObjectValue(*obj));
+ RootedValue rv(cx);
+ if (!Call(cx, callable, thisv, args, &rv))
+ return false;
+
+ *nvp = rv;
+ return true;
+}
+
+static bool
+obj_watch(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject obj(cx, ToObject(cx, args.thisv()));
+ if (!obj)
+ return false;
+
+ if (!GlobalObject::warnOnceAboutWatch(cx, obj))
+ return false;
+
+ if (args.length() <= 1) {
+ ReportMissingArg(cx, args.calleev(), 1);
+ return false;
+ }
+
+ RootedObject callable(cx, ValueToCallable(cx, args[1], args.length() - 2));
+ if (!callable)
+ return false;
+
+ RootedId propid(cx);
+ if (!ValueToId<CanGC>(cx, args[0], &propid))
+ return false;
+
+ if (!WatchProperty(cx, obj, propid, callable))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+obj_unwatch(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject obj(cx, ToObject(cx, args.thisv()));
+ if (!obj)
+ return false;
+
+ if (!GlobalObject::warnOnceAboutWatch(cx, obj))
+ return false;
+
+ RootedId id(cx);
+ if (args.length() != 0) {
+ if (!ValueToId<CanGC>(cx, args[0], &id))
+ return false;
+ } else {
+ id = JSID_VOID;
+ }
+
+ if (!UnwatchProperty(cx, obj, id))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+
+#endif /* JS_HAS_OBJ_WATCHPOINT */
+
+/* ECMA 15.2.4.5. */
+bool
+js::obj_hasOwnProperty(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ HandleValue idValue = args.get(0);
+
+ // As an optimization, provide a fast path when rooting is not necessary and
+ // we can safely retrieve the object's shape.
+
+ /* Step 1, 2. */
+ jsid id;
+ if (args.thisv().isObject() && ValueToId<NoGC>(cx, idValue, &id)) {
+ JSObject* obj = &args.thisv().toObject();
+ Shape* prop;
+ if (obj->isNative() &&
+ NativeLookupOwnProperty<NoGC>(cx, &obj->as<NativeObject>(), id, &prop))
+ {
+ args.rval().setBoolean(!!prop);
+ return true;
+ }
+ }
+
+ /* Step 1. */
+ RootedId idRoot(cx);
+ if (!ToPropertyKey(cx, idValue, &idRoot))
+ return false;
+
+ /* Step 2. */
+ RootedObject obj(cx, ToObject(cx, args.thisv()));
+ if (!obj)
+ return false;
+
+ /* Step 3. */
+ bool found;
+ if (!HasOwnProperty(cx, obj, idRoot, &found))
+ return false;
+
+ /* Step 4,5. */
+ args.rval().setBoolean(found);
+ return true;
+}
+
+/* ES5 15.2.4.6. */
+static bool
+obj_isPrototypeOf(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ /* Step 1. */
+ if (args.length() < 1 || !args[0].isObject()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ /* Step 2. */
+ RootedObject obj(cx, ToObject(cx, args.thisv()));
+ if (!obj)
+ return false;
+
+ /* Step 3. */
+ bool isDelegate;
+ if (!IsDelegate(cx, obj, args[0], &isDelegate))
+ return false;
+ args.rval().setBoolean(isDelegate);
+ return true;
+}
+
+PlainObject*
+js::ObjectCreateImpl(JSContext* cx, HandleObject proto, NewObjectKind newKind,
+ HandleObjectGroup group)
+{
+ // Give the new object a small number of fixed slots, like we do for empty
+ // object literals ({}).
+ gc::AllocKind allocKind = GuessObjectGCKind(0);
+
+ if (!proto) {
+ // Object.create(null) is common, optimize it by using an allocation
+ // site specific ObjectGroup. Because GetCallerInitGroup is pretty
+ // slow, the caller can pass in the group if it's known and we use that
+ // instead.
+ RootedObjectGroup ngroup(cx, group);
+ if (!ngroup) {
+ ngroup = ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Null);
+ if (!ngroup)
+ return nullptr;
+ }
+
+ MOZ_ASSERT(!ngroup->proto().toObjectOrNull());
+
+ return NewObjectWithGroup<PlainObject>(cx, ngroup, allocKind, newKind);
+ }
+
+ return NewObjectWithGivenProto<PlainObject>(cx, proto, allocKind, newKind);
+}
+
+PlainObject*
+js::ObjectCreateWithTemplate(JSContext* cx, HandlePlainObject templateObj)
+{
+ RootedObject proto(cx, templateObj->staticPrototype());
+ RootedObjectGroup group(cx, templateObj->group());
+ return ObjectCreateImpl(cx, proto, GenericObject, group);
+}
+
+// ES 2017 draft 19.1.2.3.1
+static bool
+ObjectDefineProperties(JSContext* cx, HandleObject obj, HandleValue properties)
+{
+ // Step 1. implicit
+ // Step 2.
+ RootedObject props(cx, ToObject(cx, properties));
+ if (!props)
+ return false;
+
+ // Step 3.
+ AutoIdVector keys(cx);
+ if (!GetPropertyKeys(cx, props, JSITER_OWNONLY | JSITER_SYMBOLS | JSITER_HIDDEN, &keys))
+ return false;
+
+ RootedId nextKey(cx);
+ Rooted<PropertyDescriptor> desc(cx);
+ RootedValue descObj(cx);
+
+ // Step 4.
+ Rooted<PropertyDescriptorVector> descriptors(cx, PropertyDescriptorVector(cx));
+ AutoIdVector descriptorKeys(cx);
+
+ // Step 5.
+ for (size_t i = 0, len = keys.length(); i < len; i++) {
+ nextKey = keys[i];
+
+ // Step 5.a.
+ if (!GetOwnPropertyDescriptor(cx, props, nextKey, &desc))
+ return false;
+
+ // Step 5.b.
+ if (desc.object() && desc.enumerable()) {
+ if (!GetProperty(cx, props, props, nextKey, &descObj) ||
+ !ToPropertyDescriptor(cx, descObj, true, &desc) ||
+ !descriptors.append(desc) ||
+ !descriptorKeys.append(nextKey))
+ {
+ return false;
+ }
+ }
+ }
+
+ // Step 6.
+ for (size_t i = 0, len = descriptors.length(); i < len; i++) {
+ if (!DefineProperty(cx, obj, descriptorKeys[i], descriptors[i]))
+ return false;
+ }
+
+ return true;
+}
+
+// ES6 draft rev34 (2015/02/20) 19.1.2.2 Object.create(O [, Properties])
+bool
+js::obj_create(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (args.length() == 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+ "Object.create", "0", "s");
+ return false;
+ }
+
+ if (!args[0].isObjectOrNull()) {
+ RootedValue v(cx, args[0]);
+ UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, nullptr);
+ if (!bytes)
+ return false;
+
+ JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
+ bytes.get(), "not an object or null");
+ return false;
+ }
+
+ // Step 2.
+ RootedObject proto(cx, args[0].toObjectOrNull());
+ RootedPlainObject obj(cx, ObjectCreateImpl(cx, proto));
+ if (!obj)
+ return false;
+
+ // Step 3.
+ if (args.hasDefined(1)) {
+ if (!ObjectDefineProperties(cx, obj, args[1]))
+ return false;
+ }
+
+ // Step 4.
+ args.rval().setObject(*obj);
+ return true;
+}
+
+// ES6 draft rev27 (2014/08/24) 19.1.2.6 Object.getOwnPropertyDescriptor(O, P)
+bool
+js::obj_getOwnPropertyDescriptor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Steps 1-2.
+ RootedObject obj(cx, ToObject(cx, args.get(0)));
+ if (!obj)
+ return false;
+
+ // Steps 3-4.
+ RootedId id(cx);
+ if (!ToPropertyKey(cx, args.get(1), &id))
+ return false;
+
+ // Steps 5-7.
+ Rooted<PropertyDescriptor> desc(cx);
+ return GetOwnPropertyDescriptor(cx, obj, id, &desc) &&
+ JS::FromPropertyDescriptor(cx, desc, args.rval());
+}
+
+enum EnumerableOwnPropertiesKind {
+ Keys,
+ Values,
+ KeysAndValues
+};
+
+// ES7 proposal 2015-12-14
+// http://tc39.github.io/proposal-object-values-entries/#EnumerableOwnProperties
+static bool
+EnumerableOwnProperties(JSContext* cx, const JS::CallArgs& args, EnumerableOwnPropertiesKind kind)
+{
+ // Step 1. (Step 1 of Object.{keys,values,entries}, really.)
+ RootedObject obj(cx, ToObject(cx, args.get(0)));
+ if (!obj)
+ return false;
+
+ // Step 2.
+ AutoIdVector ids(cx);
+ if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &ids))
+ return false;
+
+ // Step 3.
+ AutoValueVector properties(cx);
+ size_t len = ids.length();
+ if (!properties.resize(len))
+ return false;
+
+ RootedId id(cx);
+ RootedValue key(cx);
+ RootedValue value(cx);
+ RootedShape shape(cx);
+ Rooted<PropertyDescriptor> desc(cx);
+ // Step 4.
+ size_t out = 0;
+ for (size_t i = 0; i < len; i++) {
+ id = ids[i];
+
+ // Step 4.a. (Symbols were filtered out in step 2.)
+ MOZ_ASSERT(!JSID_IS_SYMBOL(id));
+
+ if (kind != Values) {
+ if (!IdToStringOrSymbol(cx, id, &key))
+ return false;
+ }
+
+ // Step 4.a.i.
+ if (obj->is<NativeObject>()) {
+ HandleNativeObject nobj = obj.as<NativeObject>();
+ if (JSID_IS_INT(id) && nobj->containsDenseElement(JSID_TO_INT(id))) {
+ value = nobj->getDenseOrTypedArrayElement(JSID_TO_INT(id));
+ } else {
+ shape = nobj->lookup(cx, id);
+ if (!shape || !(GetShapeAttributes(nobj, shape) & JSPROP_ENUMERATE))
+ continue;
+ if (!shape->isAccessorShape()) {
+ if (!NativeGetExistingProperty(cx, nobj, nobj, shape, &value))
+ return false;
+ } else if (!GetProperty(cx, obj, obj, id, &value)) {
+ return false;
+ }
+ }
+ } else {
+ if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
+ return false;
+
+ // Step 4.a.ii. (inverted.)
+ if (!desc.object() || !desc.enumerable())
+ continue;
+
+ // Step 4.a.ii.1.
+ // (Omitted because Object.keys doesn't use this implementation.)
+
+ // Step 4.a.ii.2.a.
+ if (obj->isNative() && desc.hasValue())
+ value = desc.value();
+ else if (!GetProperty(cx, obj, obj, id, &value))
+ return false;
+ }
+
+ // Steps 4.a.ii.2.b-c.
+ if (kind == Values)
+ properties[out++].set(value);
+ else if (!NewValuePair(cx, key, value, properties[out++]))
+ return false;
+ }
+
+ // Step 5.
+ // (Implemented in step 2.)
+
+ // Step 3 of Object.{keys,values,entries}
+ JSObject* aobj = NewDenseCopiedArray(cx, out, properties.begin());
+ if (!aobj)
+ return false;
+
+ args.rval().setObject(*aobj);
+ return true;
+}
+
+// ES7 proposal 2015-12-14
+// http://tc39.github.io/proposal-object-values-entries/#Object.keys
+static bool
+obj_keys(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY);
+}
+
+// ES7 proposal 2015-12-14
+// http://tc39.github.io/proposal-object-values-entries/#Object.values
+static bool
+obj_values(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return EnumerableOwnProperties(cx, args, Values);
+}
+
+// ES7 proposal 2015-12-14
+// http://tc39.github.io/proposal-object-values-entries/#Object.entries
+static bool
+obj_entries(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return EnumerableOwnProperties(cx, args, KeysAndValues);
+}
+
+/* ES6 draft 15.2.3.16 */
+static bool
+obj_is(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ bool same;
+ if (!SameValue(cx, args.get(0), args.get(1), &same))
+ return false;
+
+ args.rval().setBoolean(same);
+ return true;
+}
+
+bool
+js::IdToStringOrSymbol(JSContext* cx, HandleId id, MutableHandleValue result)
+{
+ if (JSID_IS_INT(id)) {
+ JSString* str = Int32ToString<CanGC>(cx, JSID_TO_INT(id));
+ if (!str)
+ return false;
+ result.setString(str);
+ } else if (JSID_IS_ATOM(id)) {
+ result.setString(JSID_TO_STRING(id));
+ } else {
+ result.setSymbol(JSID_TO_SYMBOL(id));
+ }
+ return true;
+}
+
+/* ES6 draft rev 25 (2014 May 22) 19.1.2.8.1 */
+bool
+js::GetOwnPropertyKeys(JSContext* cx, const JS::CallArgs& args, unsigned flags)
+{
+ // Steps 1-2.
+ RootedObject obj(cx, ToObject(cx, args.get(0)));
+ if (!obj)
+ return false;
+
+ // Steps 3-10.
+ AutoIdVector keys(cx);
+ if (!GetPropertyKeys(cx, obj, flags, &keys))
+ return false;
+
+ // Step 11.
+ AutoValueVector vals(cx);
+ if (!vals.resize(keys.length()))
+ return false;
+
+ for (size_t i = 0, len = keys.length(); i < len; i++) {
+ MOZ_ASSERT_IF(JSID_IS_SYMBOL(keys[i]), flags & JSITER_SYMBOLS);
+ MOZ_ASSERT_IF(!JSID_IS_SYMBOL(keys[i]), !(flags & JSITER_SYMBOLSONLY));
+ if (!IdToStringOrSymbol(cx, keys[i], vals[i]))
+ return false;
+ }
+
+ JSObject* aobj = NewDenseCopiedArray(cx, vals.length(), vals.begin());
+ if (!aobj)
+ return false;
+
+ args.rval().setObject(*aobj);
+ return true;
+}
+
+bool
+js::obj_getOwnPropertyNames(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY | JSITER_HIDDEN);
+}
+
+/* ES6 draft rev 25 (2014 May 22) 19.1.2.8 */
+static bool
+obj_getOwnPropertySymbols(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return GetOwnPropertyKeys(cx, args,
+ JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | JSITER_SYMBOLSONLY);
+}
+
+/* ES6 draft rev 32 (2015 Feb 2) 19.1.2.4: Object.defineProperty(O, P, Attributes) */
+bool
+js::obj_defineProperty(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Steps 1-3.
+ RootedObject obj(cx);
+ if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperty", &obj))
+ return false;
+ RootedId id(cx);
+ if (!ToPropertyKey(cx, args.get(1), &id))
+ return false;
+
+ // Steps 4-5.
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!ToPropertyDescriptor(cx, args.get(2), true, &desc))
+ return false;
+
+ // Steps 6-8.
+ if (!DefineProperty(cx, obj, id, desc))
+ return false;
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/* ES5 15.2.3.7: Object.defineProperties(O, Properties) */
+static bool
+obj_defineProperties(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ /* Steps 1 and 7. */
+ RootedObject obj(cx);
+ if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperties", &obj))
+ return false;
+ args.rval().setObject(*obj);
+
+ /* Step 2. */
+ if (args.length() < 2) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+ "Object.defineProperties", "0", "s");
+ return false;
+ }
+
+ /* Steps 3-6. */
+ return ObjectDefineProperties(cx, obj, args[1]);
+}
+
+// ES6 20141014 draft 19.1.2.15 Object.preventExtensions(O)
+static bool
+obj_preventExtensions(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().set(args.get(0));
+
+ // Step 1.
+ if (!args.get(0).isObject())
+ return true;
+
+ // Steps 2-5.
+ RootedObject obj(cx, &args.get(0).toObject());
+ return PreventExtensions(cx, obj);
+}
+
+// ES6 draft rev27 (2014/08/24) 19.1.2.5 Object.freeze(O)
+static bool
+obj_freeze(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().set(args.get(0));
+
+ // Step 1.
+ if (!args.get(0).isObject())
+ return true;
+
+ // Steps 2-5.
+ RootedObject obj(cx, &args.get(0).toObject());
+ return SetIntegrityLevel(cx, obj, IntegrityLevel::Frozen);
+}
+
+// ES6 draft rev27 (2014/08/24) 19.1.2.12 Object.isFrozen(O)
+static bool
+obj_isFrozen(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ bool frozen = true;
+
+ // Step 2.
+ if (args.get(0).isObject()) {
+ RootedObject obj(cx, &args.get(0).toObject());
+ if (!TestIntegrityLevel(cx, obj, IntegrityLevel::Frozen, &frozen))
+ return false;
+ }
+ args.rval().setBoolean(frozen);
+ return true;
+}
+
+// ES6 draft rev27 (2014/08/24) 19.1.2.17 Object.seal(O)
+static bool
+obj_seal(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().set(args.get(0));
+
+ // Step 1.
+ if (!args.get(0).isObject())
+ return true;
+
+ // Steps 2-5.
+ RootedObject obj(cx, &args.get(0).toObject());
+ return SetIntegrityLevel(cx, obj, IntegrityLevel::Sealed);
+}
+
+// ES6 draft rev27 (2014/08/24) 19.1.2.13 Object.isSealed(O)
+static bool
+obj_isSealed(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ bool sealed = true;
+
+ // Step 2.
+ if (args.get(0).isObject()) {
+ RootedObject obj(cx, &args.get(0).toObject());
+ if (!TestIntegrityLevel(cx, obj, IntegrityLevel::Sealed, &sealed))
+ return false;
+ }
+ args.rval().setBoolean(sealed);
+ return true;
+}
+
+static bool
+ProtoGetter(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedValue thisv(cx, args.thisv());
+ if (thisv.isPrimitive()) {
+ if (thisv.isNullOrUndefined()) {
+ ReportIncompatible(cx, args);
+ return false;
+ }
+
+ if (!BoxNonStrictThis(cx, thisv, &thisv))
+ return false;
+ }
+
+ RootedObject obj(cx, &thisv.toObject());
+ RootedObject proto(cx);
+ if (!GetPrototype(cx, obj, &proto))
+ return false;
+
+ args.rval().setObjectOrNull(proto);
+ return true;
+}
+
+namespace js {
+size_t sSetProtoCalled = 0;
+} // namespace js
+
+static bool
+ProtoSetter(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ HandleValue thisv = args.thisv();
+ if (thisv.isNullOrUndefined()) {
+ ReportIncompatible(cx, args);
+ return false;
+ }
+ if (thisv.isPrimitive()) {
+ // Mutating a boxed primitive's [[Prototype]] has no side effects.
+ args.rval().setUndefined();
+ return true;
+ }
+
+ if (!cx->runningWithTrustedPrincipals())
+ ++sSetProtoCalled;
+
+ Rooted<JSObject*> obj(cx, &args.thisv().toObject());
+
+ /* Do nothing if __proto__ isn't being set to an object or null. */
+ if (args.length() == 0 || !args[0].isObjectOrNull()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ Rooted<JSObject*> newProto(cx, args[0].toObjectOrNull());
+ if (!SetPrototype(cx, obj, newProto))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static const JSFunctionSpec object_methods[] = {
+#if JS_HAS_TOSOURCE
+ JS_FN(js_toSource_str, obj_toSource, 0,0),
+#endif
+ JS_FN(js_toString_str, obj_toString, 0,0),
+ JS_SELF_HOSTED_FN(js_toLocaleString_str, "Object_toLocaleString", 0, 0),
+ JS_FN(js_valueOf_str, obj_valueOf, 0,0),
+#if JS_HAS_OBJ_WATCHPOINT
+ JS_FN(js_watch_str, obj_watch, 2,0),
+ JS_FN(js_unwatch_str, obj_unwatch, 1,0),
+#endif
+ JS_FN(js_hasOwnProperty_str, obj_hasOwnProperty, 1,0),
+ JS_FN(js_isPrototypeOf_str, obj_isPrototypeOf, 1,0),
+ JS_FN(js_propertyIsEnumerable_str, obj_propertyIsEnumerable, 1,0),
+#if JS_OLD_GETTER_SETTER_METHODS
+ JS_SELF_HOSTED_FN(js_defineGetter_str, "ObjectDefineGetter", 2,0),
+ JS_SELF_HOSTED_FN(js_defineSetter_str, "ObjectDefineSetter", 2,0),
+ JS_SELF_HOSTED_FN(js_lookupGetter_str, "ObjectLookupGetter", 1,0),
+ JS_SELF_HOSTED_FN(js_lookupSetter_str, "ObjectLookupSetter", 1,0),
+#endif
+ JS_FS_END
+};
+
+static const JSPropertySpec object_properties[] = {
+#if JS_HAS_OBJ_PROTO_PROP
+ JS_PSGS("__proto__", ProtoGetter, ProtoSetter, 0),
+#endif
+ JS_PS_END
+};
+
+static const JSFunctionSpec object_static_methods[] = {
+ JS_SELF_HOSTED_FN("assign", "ObjectStaticAssign", 2, 0),
+ JS_SELF_HOSTED_FN("getPrototypeOf", "ObjectGetPrototypeOf", 1, 0),
+ JS_FN("setPrototypeOf", obj_setPrototypeOf, 2, 0),
+ JS_FN("getOwnPropertyDescriptor", obj_getOwnPropertyDescriptor,2, 0),
+ JS_SELF_HOSTED_FN("getOwnPropertyDescriptors", "ObjectGetOwnPropertyDescriptors", 1, 0),
+ JS_FN("keys", obj_keys, 1, 0),
+ JS_FN("values", obj_values, 1, 0),
+ JS_FN("entries", obj_entries, 1, 0),
+ JS_FN("is", obj_is, 2, 0),
+ JS_FN("defineProperty", obj_defineProperty, 3, 0),
+ JS_FN("defineProperties", obj_defineProperties, 2, 0),
+ JS_INLINABLE_FN("create", obj_create, 2, 0, ObjectCreate),
+ JS_FN("getOwnPropertyNames", obj_getOwnPropertyNames, 1, 0),
+ JS_FN("getOwnPropertySymbols", obj_getOwnPropertySymbols, 1, 0),
+ JS_SELF_HOSTED_FN("isExtensible", "ObjectIsExtensible", 1, 0),
+ JS_FN("preventExtensions", obj_preventExtensions, 1, 0),
+ JS_FN("freeze", obj_freeze, 1, 0),
+ JS_FN("isFrozen", obj_isFrozen, 1, 0),
+ JS_FN("seal", obj_seal, 1, 0),
+ JS_FN("isSealed", obj_isSealed, 1, 0),
+ JS_FS_END
+};
+
+static JSObject*
+CreateObjectConstructor(JSContext* cx, JSProtoKey key)
+{
+ Rooted<GlobalObject*> self(cx, cx->global());
+ if (!GlobalObject::ensureConstructor(cx, self, JSProto_Function))
+ return nullptr;
+
+ /* Create the Object function now that we have a [[Prototype]] for it. */
+ return NewNativeConstructor(cx, obj_construct, 1, HandlePropertyName(cx->names().Object),
+ gc::AllocKind::FUNCTION, SingletonObject);
+}
+
+static JSObject*
+CreateObjectPrototype(JSContext* cx, JSProtoKey key)
+{
+ MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
+ MOZ_ASSERT(cx->global()->isNative());
+
+ /*
+ * Create |Object.prototype| first, mirroring CreateBlankProto but for the
+ * prototype of the created object.
+ */
+ RootedPlainObject objectProto(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr,
+ SingletonObject));
+ if (!objectProto)
+ return nullptr;
+
+ bool succeeded;
+ if (!SetImmutablePrototype(cx, objectProto, &succeeded))
+ return nullptr;
+ MOZ_ASSERT(succeeded,
+ "should have been able to make a fresh Object.prototype's "
+ "[[Prototype]] immutable");
+
+ /*
+ * The default 'new' type of Object.prototype is required by type inference
+ * to have unknown properties, to simplify handling of e.g. heterogenous
+ * objects in JSON and script literals.
+ */
+ if (!JSObject::setNewGroupUnknown(cx, &PlainObject::class_, objectProto))
+ return nullptr;
+
+ return objectProto;
+}
+
+static bool
+FinishObjectClassInit(JSContext* cx, JS::HandleObject ctor, JS::HandleObject proto)
+{
+ Rooted<GlobalObject*> global(cx, cx->global());
+
+ /* ES5 15.1.2.1. */
+ RootedId evalId(cx, NameToId(cx->names().eval));
+ JSObject* evalobj = DefineFunction(cx, global, evalId, IndirectEval, 1,
+ JSFUN_STUB_GSOPS | JSPROP_RESOLVING);
+ if (!evalobj)
+ return false;
+ global->setOriginalEval(evalobj);
+
+ Rooted<NativeObject*> holder(cx, GlobalObject::getIntrinsicsHolder(cx, global));
+ if (!holder)
+ return false;
+
+ /*
+ * The global object should have |Object.prototype| as its [[Prototype]].
+ * Eventually we'd like to have standard classes be there from the start,
+ * and thus we would know we were always setting what had previously been a
+ * null [[Prototype]], but right now some code assumes it can set the
+ * [[Prototype]] before standard classes have been initialized. For now,
+ * only set the [[Prototype]] if it hasn't already been set.
+ */
+ Rooted<TaggedProto> tagged(cx, TaggedProto(proto));
+ if (global->shouldSplicePrototype(cx)) {
+ if (!global->splicePrototype(cx, global->getClass(), tagged))
+ return false;
+ }
+ return true;
+}
+
+static const ClassSpec PlainObjectClassSpec = {
+ CreateObjectConstructor,
+ CreateObjectPrototype,
+ object_static_methods,
+ nullptr,
+ object_methods,
+ object_properties,
+ FinishObjectClassInit
+};
+
+const Class PlainObject::class_ = {
+ js_Object_str,
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Object),
+ JS_NULL_CLASS_OPS,
+ &PlainObjectClassSpec
+};
+
+const Class* const js::ObjectClassPtr = &PlainObject::class_;
diff --git a/js/src/builtin/Object.h b/js/src/builtin/Object.h
new file mode 100644
index 000000000..09512be36
--- /dev/null
+++ b/js/src/builtin/Object.h
@@ -0,0 +1,87 @@
+/* -*- 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/. */
+
+#ifndef builtin_Object_h
+#define builtin_Object_h
+
+#include "jsapi.h"
+
+#include "vm/NativeObject.h"
+
+namespace JS {
+class CallArgs;
+class Value;
+} // namespace JS
+
+namespace js {
+
+// Object constructor native. Exposed only so the JIT can know its address.
+MOZ_MUST_USE bool
+obj_construct(JSContext* cx, unsigned argc, JS::Value* vp);
+
+MOZ_MUST_USE bool
+obj_propertyIsEnumerable(JSContext* cx, unsigned argc, Value* vp);
+
+MOZ_MUST_USE bool
+obj_valueOf(JSContext* cx, unsigned argc, JS::Value* vp);
+
+PlainObject*
+ObjectCreateImpl(JSContext* cx, HandleObject proto, NewObjectKind newKind = GenericObject,
+ HandleObjectGroup group = nullptr);
+
+PlainObject*
+ObjectCreateWithTemplate(JSContext* cx, HandlePlainObject templateObj);
+
+// Object methods exposed so they can be installed in the self-hosting global.
+MOZ_MUST_USE bool
+obj_create(JSContext* cx, unsigned argc, JS::Value* vp);
+
+MOZ_MUST_USE bool
+obj_defineProperty(JSContext* cx, unsigned argc, JS::Value* vp);
+
+MOZ_MUST_USE bool
+obj_getOwnPropertyNames(JSContext* cx, unsigned argc, JS::Value* vp);
+
+MOZ_MUST_USE bool
+obj_getOwnPropertyDescriptor(JSContext* cx, unsigned argc, JS::Value* vp);
+
+MOZ_MUST_USE bool
+obj_getPrototypeOf(JSContext* cx, unsigned argc, JS::Value* vp);
+
+MOZ_MUST_USE bool
+obj_hasOwnProperty(JSContext* cx, unsigned argc, JS::Value* vp);
+
+MOZ_MUST_USE bool
+obj_isExtensible(JSContext* cx, unsigned argc, JS::Value* vp);
+
+MOZ_MUST_USE bool
+obj_toString(JSContext* cx, unsigned argc, JS::Value* vp);
+
+// Exposed so SelfHosting.cpp can use it in the OwnPropertyKeys intrinsic
+MOZ_MUST_USE bool
+GetOwnPropertyKeys(JSContext* cx, const JS::CallArgs& args, unsigned flags);
+
+/*
+ * Like IdToValue, but convert int jsids to strings. This is used when
+ * exposing a jsid to script for Object.getOwnProperty{Names,Symbols}
+ * or scriptable proxy traps.
+ */
+MOZ_MUST_USE bool
+IdToStringOrSymbol(JSContext* cx, JS::HandleId id, JS::MutableHandleValue result);
+
+#if JS_HAS_TOSOURCE
+// Object.prototype.toSource. Function.prototype.toSource and uneval use this.
+JSString*
+ObjectToSource(JSContext* cx, JS::HandleObject obj);
+#endif // JS_HAS_TOSOURCE
+
+extern MOZ_MUST_USE bool
+WatchHandler(JSContext* cx, JSObject* obj, jsid id, const JS::Value& old,
+ JS::Value* nvp, void* closure);
+
+} /* namespace js */
+
+#endif /* builtin_Object_h */
diff --git a/js/src/builtin/Object.js b/js/src/builtin/Object.js
new file mode 100644
index 000000000..a7440aec7
--- /dev/null
+++ b/js/src/builtin/Object.js
@@ -0,0 +1,198 @@
+/* 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/. */
+
+// ES6 draft rev36 2015-03-17 19.1.2.1
+function ObjectStaticAssign(target, firstSource) {
+ // Steps 1-2.
+ var to = ToObject(target);
+
+ // Step 3.
+ if (arguments.length < 2)
+ return to;
+
+ // Steps 4-5.
+ for (var i = 1; i < arguments.length; i++) {
+ // Step 5.a.
+ var nextSource = arguments[i];
+ if (nextSource === null || nextSource === undefined)
+ continue;
+
+ // Steps 5.b.i-ii.
+ var from = ToObject(nextSource);
+
+ // Steps 5.b.iii-iv.
+ var keys = OwnPropertyKeys(from, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS);
+
+ // Step 5.c.
+ for (var nextIndex = 0, len = keys.length; nextIndex < len; nextIndex++) {
+ var nextKey = keys[nextIndex];
+
+ // Steps 5.c.i-iii. We abbreviate this by calling propertyIsEnumerable
+ // which is faster and returns false for not defined properties.
+ if (callFunction(std_Object_propertyIsEnumerable, from, nextKey)) {
+ // Steps 5.c.iii.1-4.
+ to[nextKey] = from[nextKey];
+ }
+ }
+ }
+
+ // Step 6.
+ return to;
+}
+
+// ES stage 4 proposal
+function ObjectGetOwnPropertyDescriptors(O) {
+ // Step 1.
+ var obj = ToObject(O);
+
+ // Step 2.
+ var keys = OwnPropertyKeys(obj, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS);
+
+ // Step 3.
+ var descriptors = {};
+
+ // Step 4.
+ for (var index = 0, len = keys.length; index < len; index++) {
+ var key = keys[index];
+
+ // Steps 4.a-b.
+ var desc = std_Object_getOwnPropertyDescriptor(obj, key);
+
+ // Step 4.c.
+ if (typeof desc !== "undefined")
+ _DefineDataProperty(descriptors, key, desc);
+ }
+
+ // Step 5.
+ return descriptors;
+}
+
+/* ES6 draft rev 32 (2015 Feb 2) 19.1.2.9. */
+function ObjectGetPrototypeOf(obj) {
+ return std_Reflect_getPrototypeOf(ToObject(obj));
+}
+
+/* ES6 draft rev 32 (2015 Feb 2) 19.1.2.11. */
+function ObjectIsExtensible(obj) {
+ return IsObject(obj) && std_Reflect_isExtensible(obj);
+}
+
+/* ES2015 19.1.3.5 Object.prototype.toLocaleString */
+function Object_toLocaleString() {
+ // Step 1.
+ var O = this;
+
+ // Step 2.
+ return callContentFunction(O.toString, O);
+}
+
+// ES7 draft (2016 March 8) B.2.2.3
+function ObjectDefineSetter(name, setter) {
+ // Step 1.
+ var object = ToObject(this);
+
+ // Step 2.
+ if (!IsCallable(setter))
+ ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "setter");
+
+ // Step 3.
+ var desc = {
+ __proto__: null,
+ enumerable: true,
+ configurable: true,
+ set: setter
+ };
+
+ // Step 4.
+ var key = ToPropertyKey(name);
+
+ // Step 5.
+ std_Object_defineProperty(object, key, desc);
+
+ // Step 6. (implicit)
+}
+
+// ES7 draft (2016 March 8) B.2.2.2
+function ObjectDefineGetter(name, getter) {
+ // Step 1.
+ var object = ToObject(this);
+
+ // Step 2.
+ if (!IsCallable(getter))
+ ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "getter");
+
+ // Step 3.
+ var desc = {
+ __proto__: null,
+ enumerable: true,
+ configurable: true,
+ get: getter
+ };
+
+ // Step 4.
+ var key = ToPropertyKey(name);
+
+ // Step 5.
+ std_Object_defineProperty(object, key, desc);
+
+ // Step 6. (implicit)
+}
+
+// ES7 draft (2016 March 8) B.2.2.5
+function ObjectLookupSetter(name) {
+ // Step 1.
+ var object = ToObject(this);
+
+ // Step 2.
+ var key = ToPropertyKey(name)
+
+ do {
+ // Step 3.a.
+ var desc = std_Object_getOwnPropertyDescriptor(object, key);
+
+ // Step 3.b.
+ if (desc) {
+ // Step.b.i.
+ if (callFunction(std_Object_hasOwnProperty, desc, "set"))
+ return desc.set;
+
+ // Step.b.ii.
+ return undefined;
+ }
+
+ // Step 3.c.
+ object = std_Reflect_getPrototypeOf(object);
+ } while (object !== null);
+
+ // Step 3.d. (implicit)
+}
+
+// ES7 draft (2016 March 8) B.2.2.4
+function ObjectLookupGetter(name) {
+ // Step 1.
+ var object = ToObject(this);
+
+ // Step 2.
+ var key = ToPropertyKey(name)
+
+ do {
+ // Step 3.a.
+ var desc = std_Object_getOwnPropertyDescriptor(object, key);
+
+ // Step 3.b.
+ if (desc) {
+ // Step.b.i.
+ if (callFunction(std_Object_hasOwnProperty, desc, "get"))
+ return desc.get;
+
+ // Step.b.ii.
+ return undefined;
+ }
+
+ // Step 3.c.
+ object = std_Reflect_getPrototypeOf(object);
+ } while (object !== null);
+
+ // Step 3.d. (implicit)
+}
diff --git a/js/src/builtin/Profilers.cpp b/js/src/builtin/Profilers.cpp
new file mode 100644
index 000000000..0d30a1c6b
--- /dev/null
+++ b/js/src/builtin/Profilers.cpp
@@ -0,0 +1,576 @@
+/* -*- 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/. */
+
+/* Profiling-related API */
+
+#include "builtin/Profilers.h"
+
+#include "mozilla/Sprintf.h"
+
+#include <stdarg.h>
+
+#ifdef MOZ_CALLGRIND
+# include <valgrind/callgrind.h>
+#endif
+
+#ifdef __APPLE__
+#ifdef MOZ_INSTRUMENTS
+# include "devtools/Instruments.h"
+#endif
+#endif
+
+#ifdef XP_WIN
+# include <process.h>
+# define getpid _getpid
+#endif
+
+#include "vm/Probes.h"
+
+#include "jscntxtinlines.h"
+
+using namespace js;
+
+using mozilla::ArrayLength;
+
+/* Thread-unsafe error management */
+
+static char gLastError[2000];
+
+#if defined(__APPLE__) || defined(__linux__) || defined(MOZ_CALLGRIND)
+static void
+MOZ_FORMAT_PRINTF(1, 2)
+UnsafeError(const char* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ (void) VsprintfLiteral(gLastError, format, args);
+ va_end(args);
+}
+#endif
+
+JS_PUBLIC_API(const char*)
+JS_UnsafeGetLastProfilingError()
+{
+ return gLastError;
+}
+
+#ifdef __APPLE__
+static bool
+StartOSXProfiling(const char* profileName, pid_t pid)
+{
+ bool ok = true;
+ const char* profiler = nullptr;
+#ifdef MOZ_INSTRUMENTS
+ ok = Instruments::Start(pid);
+ profiler = "Instruments";
+#endif
+ if (!ok) {
+ if (profileName)
+ UnsafeError("Failed to start %s for %s", profiler, profileName);
+ else
+ UnsafeError("Failed to start %s", profiler);
+ return false;
+ }
+ return true;
+}
+#endif
+
+JS_PUBLIC_API(bool)
+JS_StartProfiling(const char* profileName, pid_t pid)
+{
+ bool ok = true;
+#ifdef __APPLE__
+ ok = StartOSXProfiling(profileName, pid);
+#endif
+#ifdef __linux__
+ if (!js_StartPerf())
+ ok = false;
+#endif
+ return ok;
+}
+
+JS_PUBLIC_API(bool)
+JS_StopProfiling(const char* profileName)
+{
+ bool ok = true;
+#ifdef __APPLE__
+#ifdef MOZ_INSTRUMENTS
+ Instruments::Stop(profileName);
+#endif
+#endif
+#ifdef __linux__
+ if (!js_StopPerf())
+ ok = false;
+#endif
+ return ok;
+}
+
+/*
+ * Start or stop whatever platform- and configuration-specific profiling
+ * backends are available.
+ */
+static bool
+ControlProfilers(bool toState)
+{
+ bool ok = true;
+
+ if (! probes::ProfilingActive && toState) {
+#ifdef __APPLE__
+#if defined(MOZ_INSTRUMENTS)
+ const char* profiler;
+#ifdef MOZ_INSTRUMENTS
+ ok = Instruments::Resume();
+ profiler = "Instruments";
+#endif
+ if (!ok) {
+ UnsafeError("Failed to start %s", profiler);
+ }
+#endif
+#endif
+#ifdef MOZ_CALLGRIND
+ if (! js_StartCallgrind()) {
+ UnsafeError("Failed to start Callgrind");
+ ok = false;
+ }
+#endif
+ } else if (probes::ProfilingActive && ! toState) {
+#ifdef __APPLE__
+#ifdef MOZ_INSTRUMENTS
+ Instruments::Pause();
+#endif
+#endif
+#ifdef MOZ_CALLGRIND
+ if (! js_StopCallgrind()) {
+ UnsafeError("failed to stop Callgrind");
+ ok = false;
+ }
+#endif
+ }
+
+ probes::ProfilingActive = toState;
+
+ return ok;
+}
+
+/*
+ * Pause/resume whatever profiling mechanism is currently compiled
+ * in, if applicable. This will not affect things like dtrace.
+ *
+ * Do not mix calls to these APIs with calls to the individual
+ * profilers' pause/resume functions, because only overall state is
+ * tracked, not the state of each profiler.
+ */
+JS_PUBLIC_API(bool)
+JS_PauseProfilers(const char* profileName)
+{
+ return ControlProfilers(false);
+}
+
+JS_PUBLIC_API(bool)
+JS_ResumeProfilers(const char* profileName)
+{
+ return ControlProfilers(true);
+}
+
+JS_PUBLIC_API(bool)
+JS_DumpProfile(const char* outfile, const char* profileName)
+{
+ bool ok = true;
+#ifdef MOZ_CALLGRIND
+ js_DumpCallgrind(outfile);
+#endif
+ return ok;
+}
+
+#ifdef MOZ_PROFILING
+
+struct RequiredStringArg {
+ JSContext* mCx;
+ char* mBytes;
+ RequiredStringArg(JSContext* cx, const CallArgs& args, size_t argi, const char* caller)
+ : mCx(cx), mBytes(nullptr)
+ {
+ if (args.length() <= argi) {
+ JS_ReportErrorASCII(cx, "%s: not enough arguments", caller);
+ } else if (!args[argi].isString()) {
+ JS_ReportErrorASCII(cx, "%s: invalid arguments (string expected)", caller);
+ } else {
+ mBytes = JS_EncodeString(cx, args[argi].toString());
+ }
+ }
+ operator void*() {
+ return (void*) mBytes;
+ }
+ ~RequiredStringArg() {
+ js_free(mBytes);
+ }
+};
+
+static bool
+StartProfiling(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() == 0) {
+ args.rval().setBoolean(JS_StartProfiling(nullptr, getpid()));
+ return true;
+ }
+
+ RequiredStringArg profileName(cx, args, 0, "startProfiling");
+ if (!profileName)
+ return false;
+
+ if (args.length() == 1) {
+ args.rval().setBoolean(JS_StartProfiling(profileName.mBytes, getpid()));
+ return true;
+ }
+
+ if (!args[1].isInt32()) {
+ JS_ReportErrorASCII(cx, "startProfiling: invalid arguments (int expected)");
+ return false;
+ }
+ pid_t pid = static_cast<pid_t>(args[1].toInt32());
+ args.rval().setBoolean(JS_StartProfiling(profileName.mBytes, pid));
+ return true;
+}
+
+static bool
+StopProfiling(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() == 0) {
+ args.rval().setBoolean(JS_StopProfiling(nullptr));
+ return true;
+ }
+
+ RequiredStringArg profileName(cx, args, 0, "stopProfiling");
+ if (!profileName)
+ return false;
+ args.rval().setBoolean(JS_StopProfiling(profileName.mBytes));
+ return true;
+}
+
+static bool
+PauseProfilers(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() == 0) {
+ args.rval().setBoolean(JS_PauseProfilers(nullptr));
+ return true;
+ }
+
+ RequiredStringArg profileName(cx, args, 0, "pauseProfiling");
+ if (!profileName)
+ return false;
+ args.rval().setBoolean(JS_PauseProfilers(profileName.mBytes));
+ return true;
+}
+
+static bool
+ResumeProfilers(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() == 0) {
+ args.rval().setBoolean(JS_ResumeProfilers(nullptr));
+ return true;
+ }
+
+ RequiredStringArg profileName(cx, args, 0, "resumeProfiling");
+ if (!profileName)
+ return false;
+ args.rval().setBoolean(JS_ResumeProfilers(profileName.mBytes));
+ return true;
+}
+
+/* Usage: DumpProfile([filename[, profileName]]) */
+static bool
+DumpProfile(JSContext* cx, unsigned argc, Value* vp)
+{
+ bool ret;
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() == 0) {
+ ret = JS_DumpProfile(nullptr, nullptr);
+ } else {
+ RequiredStringArg filename(cx, args, 0, "dumpProfile");
+ if (!filename)
+ return false;
+
+ if (args.length() == 1) {
+ ret = JS_DumpProfile(filename.mBytes, nullptr);
+ } else {
+ RequiredStringArg profileName(cx, args, 1, "dumpProfile");
+ if (!profileName)
+ return false;
+
+ ret = JS_DumpProfile(filename.mBytes, profileName.mBytes);
+ }
+ }
+
+ args.rval().setBoolean(ret);
+ return true;
+}
+
+static bool
+GetMaxGCPauseSinceClear(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setNumber(uint32_t(cx->runtime()->gc.stats.getMaxGCPauseSinceClear()));
+ return true;
+}
+
+static bool
+ClearMaxGCPauseAccumulator(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setNumber(uint32_t(cx->runtime()->gc.stats.clearMaxGCPauseAccumulator()));
+ return true;
+}
+
+#if defined(MOZ_INSTRUMENTS)
+
+static bool
+IgnoreAndReturnTrue(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(true);
+ return true;
+}
+
+#endif
+
+#ifdef MOZ_CALLGRIND
+static bool
+StartCallgrind(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(js_StartCallgrind());
+ return true;
+}
+
+static bool
+StopCallgrind(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(js_StopCallgrind());
+ return true;
+}
+
+static bool
+DumpCallgrind(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() == 0) {
+ args.rval().setBoolean(js_DumpCallgrind(nullptr));
+ return true;
+ }
+
+ RequiredStringArg outFile(cx, args, 0, "dumpCallgrind");
+ if (!outFile)
+ return false;
+
+ args.rval().setBoolean(js_DumpCallgrind(outFile.mBytes));
+ return true;
+}
+#endif
+
+static const JSFunctionSpec profiling_functions[] = {
+ JS_FN("startProfiling", StartProfiling, 1,0),
+ JS_FN("stopProfiling", StopProfiling, 1,0),
+ JS_FN("pauseProfilers", PauseProfilers, 1,0),
+ JS_FN("resumeProfilers", ResumeProfilers, 1,0),
+ JS_FN("dumpProfile", DumpProfile, 2,0),
+ JS_FN("getMaxGCPauseSinceClear", GetMaxGCPauseSinceClear, 0, 0),
+ JS_FN("clearMaxGCPauseAccumulator", ClearMaxGCPauseAccumulator, 0, 0),
+#if defined(MOZ_INSTRUMENTS)
+ /* Keep users of the old shark API happy. */
+ JS_FN("connectShark", IgnoreAndReturnTrue, 0,0),
+ JS_FN("disconnectShark", IgnoreAndReturnTrue, 0,0),
+ JS_FN("startShark", StartProfiling, 0,0),
+ JS_FN("stopShark", StopProfiling, 0,0),
+#endif
+#ifdef MOZ_CALLGRIND
+ JS_FN("startCallgrind", StartCallgrind, 0,0),
+ JS_FN("stopCallgrind", StopCallgrind, 0,0),
+ JS_FN("dumpCallgrind", DumpCallgrind, 1,0),
+#endif
+ JS_FS_END
+};
+
+#endif
+
+JS_PUBLIC_API(bool)
+JS_DefineProfilingFunctions(JSContext* cx, HandleObject obj)
+{
+ assertSameCompartment(cx, obj);
+#ifdef MOZ_PROFILING
+ return JS_DefineFunctions(cx, obj, profiling_functions);
+#else
+ return true;
+#endif
+}
+
+#ifdef MOZ_CALLGRIND
+
+JS_FRIEND_API(bool)
+js_StartCallgrind()
+{
+ JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_START_INSTRUMENTATION);
+ JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_ZERO_STATS);
+ return true;
+}
+
+JS_FRIEND_API(bool)
+js_StopCallgrind()
+{
+ JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_STOP_INSTRUMENTATION);
+ return true;
+}
+
+JS_FRIEND_API(bool)
+js_DumpCallgrind(const char* outfile)
+{
+ if (outfile) {
+ JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_DUMP_STATS_AT(outfile));
+ } else {
+ JS_SILENCE_UNUSED_VALUE_IN_EXPR(CALLGRIND_DUMP_STATS);
+ }
+
+ return true;
+}
+
+#endif /* MOZ_CALLGRIND */
+
+#ifdef __linux__
+
+/*
+ * Code for starting and stopping |perf|, the Linux profiler.
+ *
+ * Output from profiling is written to mozperf.data in your cwd.
+ *
+ * To enable, set MOZ_PROFILE_WITH_PERF=1 in your environment.
+ *
+ * To pass additional parameters to |perf record|, provide them in the
+ * MOZ_PROFILE_PERF_FLAGS environment variable. If this variable does not
+ * exist, we default it to "--call-graph". (If you don't want --call-graph but
+ * don't want to pass any other args, define MOZ_PROFILE_PERF_FLAGS to the empty
+ * string.)
+ *
+ * If you include --pid or --output in MOZ_PROFILE_PERF_FLAGS, you're just
+ * asking for trouble.
+ *
+ * Our split-on-spaces logic is lame, so don't expect MOZ_PROFILE_PERF_FLAGS to
+ * work if you pass an argument which includes a space (e.g.
+ * MOZ_PROFILE_PERF_FLAGS="-e 'foo bar'").
+ */
+
+#include <signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+static bool perfInitialized = false;
+static pid_t perfPid = 0;
+
+bool js_StartPerf()
+{
+ const char* outfile = "mozperf.data";
+
+ if (perfPid != 0) {
+ UnsafeError("js_StartPerf: called while perf was already running!\n");
+ return false;
+ }
+
+ // Bail if MOZ_PROFILE_WITH_PERF is empty or undefined.
+ if (!getenv("MOZ_PROFILE_WITH_PERF") ||
+ !strlen(getenv("MOZ_PROFILE_WITH_PERF"))) {
+ return true;
+ }
+
+ /*
+ * Delete mozperf.data the first time through -- we're going to append to it
+ * later on, so we want it to be clean when we start out.
+ */
+ if (!perfInitialized) {
+ perfInitialized = true;
+ unlink(outfile);
+ char cwd[4096];
+ printf("Writing perf profiling data to %s/%s\n",
+ getcwd(cwd, sizeof(cwd)), outfile);
+ }
+
+ pid_t mainPid = getpid();
+
+ pid_t childPid = fork();
+ if (childPid == 0) {
+ /* perf record --pid $mainPID --output=$outfile $MOZ_PROFILE_PERF_FLAGS */
+
+ char mainPidStr[16];
+ SprintfLiteral(mainPidStr, "%d", mainPid);
+ const char* defaultArgs[] = {"perf", "record", "--pid", mainPidStr, "--output", outfile};
+
+ Vector<const char*, 0, SystemAllocPolicy> args;
+ if (!args.append(defaultArgs, ArrayLength(defaultArgs)))
+ return false;
+
+ const char* flags = getenv("MOZ_PROFILE_PERF_FLAGS");
+ if (!flags) {
+ flags = "--call-graph";
+ }
+
+ UniqueChars flags2((char*)js_malloc(strlen(flags) + 1));
+ if (!flags2)
+ return false;
+ strcpy(flags2.get(), flags);
+
+ // Split |flags2| on spaces.
+ char* toksave;
+ char* tok = strtok_r(flags2.get(), " ", &toksave);
+ while (tok) {
+ if (!args.append(tok))
+ return false;
+ tok = strtok_r(nullptr, " ", &toksave);
+ }
+
+ if (!args.append((char*) nullptr))
+ return false;
+
+ execvp("perf", const_cast<char**>(args.begin()));
+
+ /* Reached only if execlp fails. */
+ fprintf(stderr, "Unable to start perf.\n");
+ exit(1);
+ }
+ if (childPid > 0) {
+ perfPid = childPid;
+
+ /* Give perf a chance to warm up. */
+ usleep(500 * 1000);
+ return true;
+ }
+ UnsafeError("js_StartPerf: fork() failed\n");
+ return false;
+}
+
+bool js_StopPerf()
+{
+ if (perfPid == 0) {
+ UnsafeError("js_StopPerf: perf is not running.\n");
+ return true;
+ }
+
+ if (kill(perfPid, SIGINT)) {
+ UnsafeError("js_StopPerf: kill failed\n");
+
+ // Try to reap the process anyway.
+ waitpid(perfPid, nullptr, WNOHANG);
+ }
+ else {
+ waitpid(perfPid, nullptr, 0);
+ }
+
+ perfPid = 0;
+ return true;
+}
+
+#endif /* __linux__ */
diff --git a/js/src/builtin/Profilers.h b/js/src/builtin/Profilers.h
new file mode 100644
index 000000000..ecd0d0982
--- /dev/null
+++ b/js/src/builtin/Profilers.h
@@ -0,0 +1,95 @@
+/* -*- 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/. */
+
+/*
+ * Functions for controlling profilers from within JS: Valgrind, Perf,
+ * Shark, etc.
+ */
+#ifndef builtin_Profilers_h
+#define builtin_Profilers_h
+
+#include "jstypes.h"
+
+#ifdef _MSC_VER
+typedef int pid_t;
+#else
+#include <unistd.h>
+#endif
+
+/**
+ * Start any profilers that are available and have been configured on for this
+ * platform. This is NOT thread safe.
+ *
+ * The profileName is used by some profilers to describe the current profiling
+ * run. It may be used for part of the filename of the output, but the
+ * specifics depend on the profiler. Many profilers will ignore it. Passing in
+ * nullptr is legal; some profilers may use it to output to stdout or similar.
+ *
+ * Returns true if no profilers fail to start.
+ */
+extern MOZ_MUST_USE JS_PUBLIC_API(bool)
+JS_StartProfiling(const char* profileName, pid_t pid);
+
+/**
+ * Stop any profilers that were previously started with JS_StartProfiling.
+ * Returns true if no profilers fail to stop.
+ */
+extern MOZ_MUST_USE JS_PUBLIC_API(bool)
+JS_StopProfiling(const char* profileName);
+
+/**
+ * Write the current profile data to the given file, if applicable to whatever
+ * profiler is being used.
+ */
+extern MOZ_MUST_USE JS_PUBLIC_API(bool)
+JS_DumpProfile(const char* outfile, const char* profileName);
+
+/**
+ * Pause currently active profilers (only supported by some profilers). Returns
+ * whether any profilers failed to pause. (Profilers that do not support
+ * pause/resume do not count.)
+ */
+extern MOZ_MUST_USE JS_PUBLIC_API(bool)
+JS_PauseProfilers(const char* profileName);
+
+/**
+ * Resume suspended profilers
+ */
+extern MOZ_MUST_USE JS_PUBLIC_API(bool)
+JS_ResumeProfilers(const char* profileName);
+
+/**
+ * The profiling API calls are not able to report errors, so they use a
+ * thread-unsafe global memory buffer to hold the last error encountered. This
+ * should only be called after something returns false.
+ */
+JS_PUBLIC_API(const char*)
+JS_UnsafeGetLastProfilingError();
+
+#ifdef MOZ_CALLGRIND
+
+extern MOZ_MUST_USE JS_FRIEND_API(bool)
+js_StopCallgrind();
+
+extern MOZ_MUST_USE JS_FRIEND_API(bool)
+js_StartCallgrind();
+
+extern MOZ_MUST_USE JS_FRIEND_API(bool)
+js_DumpCallgrind(const char* outfile);
+
+#endif /* MOZ_CALLGRIND */
+
+#ifdef __linux__
+
+extern MOZ_MUST_USE JS_FRIEND_API(bool)
+js_StartPerf();
+
+extern MOZ_MUST_USE JS_FRIEND_API(bool)
+js_StopPerf();
+
+#endif /* __linux__ */
+
+#endif /* builtin_Profilers_h */
diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp
new file mode 100644
index 000000000..59c97e529
--- /dev/null
+++ b/js/src/builtin/Promise.cpp
@@ -0,0 +1,2794 @@
+/* -*- 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 "builtin/Promise.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/TimeStamp.h"
+
+#include "jscntxt.h"
+
+#include "gc/Heap.h"
+#include "js/Debug.h"
+#include "vm/AsyncFunction.h"
+#include "vm/SelfHosting.h"
+
+#include "jsobjinlines.h"
+
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+static double
+MillisecondsSinceStartup()
+{
+ auto now = mozilla::TimeStamp::Now();
+ bool ignored;
+ return (now - mozilla::TimeStamp::ProcessCreation(ignored)).ToMilliseconds();
+}
+
+#define PROMISE_HANDLER_IDENTITY 0
+#define PROMISE_HANDLER_THROWER 1
+#define PROMISE_HANDLER_AWAIT_FULFILLED 2
+#define PROMISE_HANDLER_AWAIT_REJECTED 3
+
+enum ResolutionMode {
+ ResolveMode,
+ RejectMode
+};
+
+enum ResolveFunctionSlots {
+ ResolveFunctionSlot_Promise = 0,
+ ResolveFunctionSlot_RejectFunction,
+};
+
+enum RejectFunctionSlots {
+ RejectFunctionSlot_Promise = ResolveFunctionSlot_Promise,
+ RejectFunctionSlot_ResolveFunction,
+ RejectFunctionSlot_PromiseAllData = RejectFunctionSlot_ResolveFunction,
+};
+
+enum PromiseAllResolveElementFunctionSlots {
+ PromiseAllResolveElementFunctionSlot_Data = 0,
+ PromiseAllResolveElementFunctionSlot_ElementIndex,
+};
+
+enum ReactionJobSlots {
+ ReactionJobSlot_ReactionRecord = 0,
+};
+
+enum ThenableJobSlots {
+ ThenableJobSlot_Handler = 0,
+ ThenableJobSlot_JobData,
+};
+
+enum ThenableJobDataIndices {
+ ThenableJobDataIndex_Promise = 0,
+ ThenableJobDataIndex_Thenable,
+ ThenableJobDataLength,
+};
+
+enum PromiseAllDataHolderSlots {
+ PromiseAllDataHolderSlot_Promise = 0,
+ PromiseAllDataHolderSlot_RemainingElements,
+ PromiseAllDataHolderSlot_ValuesArray,
+ PromiseAllDataHolderSlot_ResolveFunction,
+ PromiseAllDataHolderSlots,
+};
+
+class PromiseAllDataHolder : public NativeObject
+{
+ public:
+ static const Class class_;
+ JSObject* promiseObj() { return &getFixedSlot(PromiseAllDataHolderSlot_Promise).toObject(); }
+ JSObject* resolveObj() {
+ return getFixedSlot(PromiseAllDataHolderSlot_ResolveFunction).toObjectOrNull();
+ }
+ Value valuesArray() { return getFixedSlot(PromiseAllDataHolderSlot_ValuesArray); }
+ int32_t remainingCount() {
+ return getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32();
+ }
+ int32_t increaseRemainingCount() {
+ int32_t remainingCount = getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32();
+ remainingCount++;
+ setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(remainingCount));
+ return remainingCount;
+ }
+ int32_t decreaseRemainingCount() {
+ int32_t remainingCount = getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32();
+ remainingCount--;
+ setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(remainingCount));
+ return remainingCount;
+ }
+};
+
+const Class PromiseAllDataHolder::class_ = {
+ "PromiseAllDataHolder",
+ JSCLASS_HAS_RESERVED_SLOTS(PromiseAllDataHolderSlots)
+};
+
+static PromiseAllDataHolder*
+NewPromiseAllDataHolder(JSContext* cx, HandleObject resultPromise, HandleValue valuesArray,
+ HandleObject resolve)
+{
+ Rooted<PromiseAllDataHolder*> dataHolder(cx, NewObjectWithClassProto<PromiseAllDataHolder>(cx));
+ if (!dataHolder)
+ return nullptr;
+
+ assertSameCompartment(cx, resultPromise);
+ assertSameCompartment(cx, valuesArray);
+ assertSameCompartment(cx, resolve);
+
+ dataHolder->setFixedSlot(PromiseAllDataHolderSlot_Promise, ObjectValue(*resultPromise));
+ dataHolder->setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(1));
+ dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ValuesArray, valuesArray);
+ dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ResolveFunction, ObjectOrNullValue(resolve));
+ return dataHolder;
+}
+
+/**
+ * Wrapper for GetAndClearException that handles cases where no exception is
+ * pending, but an error occurred. This can be the case if an OOM was
+ * encountered while throwing the error.
+ */
+static bool
+MaybeGetAndClearException(JSContext* cx, MutableHandleValue rval)
+{
+ if (!cx->isExceptionPending())
+ return false;
+
+ return GetAndClearException(cx, rval);
+}
+
+static MOZ_MUST_USE bool RunResolutionFunction(JSContext *cx, HandleObject resolutionFun,
+ HandleValue result, ResolutionMode mode,
+ HandleObject promiseObj);
+
+// ES2016, 25.4.1.1.1, Steps 1.a-b.
+// Extracting all of this internal spec algorithm into a helper function would
+// be tedious, so the check in step 1 and the entirety of step 2 aren't
+// included.
+static bool
+AbruptRejectPromise(JSContext *cx, CallArgs& args, HandleObject promiseObj, HandleObject reject)
+{
+ // Step 1.a.
+ RootedValue reason(cx);
+ if (!MaybeGetAndClearException(cx, &reason))
+ return false;
+
+ if (!RunResolutionFunction(cx, reject, reason, RejectMode, promiseObj))
+ return false;
+
+ // Step 1.b.
+ args.rval().setObject(*promiseObj);
+ return true;
+}
+
+enum ReactionRecordSlots {
+ ReactionRecordSlot_Promise = 0,
+ ReactionRecordSlot_OnFulfilled,
+ ReactionRecordSlot_OnRejected,
+ ReactionRecordSlot_Resolve,
+ ReactionRecordSlot_Reject,
+ ReactionRecordSlot_IncumbentGlobalObject,
+ ReactionRecordSlot_Flags,
+ ReactionRecordSlot_HandlerArg,
+ ReactionRecordSlots,
+};
+
+#define REACTION_FLAG_RESOLVED 0x1
+#define REACTION_FLAG_FULFILLED 0x2
+#define REACTION_FLAG_IGNORE_DEFAULT_RESOLUTION 0x4
+#define REACTION_FLAG_AWAIT 0x8
+
+// ES2016, 25.4.1.2.
+class PromiseReactionRecord : public NativeObject
+{
+ public:
+ static const Class class_;
+
+ JSObject* promise() { return getFixedSlot(ReactionRecordSlot_Promise).toObjectOrNull(); }
+ int32_t flags() { return getFixedSlot(ReactionRecordSlot_Flags).toInt32(); }
+ JS::PromiseState targetState() {
+ int32_t flags = this->flags();
+ if (!(flags & REACTION_FLAG_RESOLVED))
+ return JS::PromiseState::Pending;
+ return flags & REACTION_FLAG_FULFILLED
+ ? JS::PromiseState::Fulfilled
+ : JS::PromiseState::Rejected;
+ }
+ void setTargetState(JS::PromiseState state) {
+ int32_t flags = this->flags();
+ MOZ_ASSERT(!(flags & REACTION_FLAG_RESOLVED));
+ MOZ_ASSERT(state != JS::PromiseState::Pending, "Can't revert a reaction to pending.");
+ flags |= REACTION_FLAG_RESOLVED;
+ if (state == JS::PromiseState::Fulfilled)
+ flags |= REACTION_FLAG_FULFILLED;
+ setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
+ }
+ void setIsAwait() {
+ int32_t flags = this->flags();
+ flags |= REACTION_FLAG_AWAIT;
+ setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
+ }
+ bool isAwait() {
+ int32_t flags = this->flags();
+ return flags & REACTION_FLAG_AWAIT;
+ }
+ Value handler() {
+ MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
+ uint32_t slot = targetState() == JS::PromiseState::Fulfilled
+ ? ReactionRecordSlot_OnFulfilled
+ : ReactionRecordSlot_OnRejected;
+ return getFixedSlot(slot);
+ }
+ Value handlerArg() {
+ MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
+ return getFixedSlot(ReactionRecordSlot_HandlerArg);
+ }
+ void setHandlerArg(Value& arg) {
+ MOZ_ASSERT(targetState() == JS::PromiseState::Pending);
+ setFixedSlot(ReactionRecordSlot_HandlerArg, arg);
+ }
+ JSObject* incumbentGlobalObject() {
+ return getFixedSlot(ReactionRecordSlot_IncumbentGlobalObject).toObjectOrNull();
+ }
+};
+
+const Class PromiseReactionRecord::class_ = {
+ "PromiseReactionRecord",
+ JSCLASS_HAS_RESERVED_SLOTS(ReactionRecordSlots)
+};
+
+static void
+AddPromiseFlags(PromiseObject& promise, int32_t flag)
+{
+ int32_t flags = promise.getFixedSlot(PromiseSlot_Flags).toInt32();
+ promise.setFixedSlot(PromiseSlot_Flags, Int32Value(flags | flag));
+}
+
+static bool
+PromiseHasAnyFlag(PromiseObject& promise, int32_t flag)
+{
+ return promise.getFixedSlot(PromiseSlot_Flags).toInt32() & flag;
+}
+
+static bool ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp);
+static bool RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp);
+
+// ES2016, 25.4.1.3.
+static MOZ_MUST_USE bool
+CreateResolvingFunctions(JSContext* cx, HandleValue promise,
+ MutableHandleValue resolveVal,
+ MutableHandleValue rejectVal)
+{
+ RootedAtom funName(cx, cx->names().empty);
+ RootedFunction resolve(cx, NewNativeFunction(cx, ResolvePromiseFunction, 1, funName,
+ gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
+ if (!resolve)
+ return false;
+
+ RootedFunction reject(cx, NewNativeFunction(cx, RejectPromiseFunction, 1, funName,
+ gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
+ if (!reject)
+ return false;
+
+ // TODO: remove this once all self-hosted promise code is gone.
+ // The resolving functions are trusted, so self-hosted code should be able
+ // to call them using callFunction instead of callContentFunction.
+ resolve->setFlags(resolve->flags() | JSFunction::SELF_HOSTED);
+ reject->setFlags(reject->flags() | JSFunction::SELF_HOSTED);
+
+ resolve->setExtendedSlot(ResolveFunctionSlot_Promise, promise);
+ resolve->setExtendedSlot(ResolveFunctionSlot_RejectFunction, ObjectValue(*reject));
+
+ reject->setExtendedSlot(RejectFunctionSlot_Promise, promise);
+ reject->setExtendedSlot(RejectFunctionSlot_ResolveFunction, ObjectValue(*resolve));
+
+ resolveVal.setObject(*resolve);
+ rejectVal.setObject(*reject);
+
+ return true;
+}
+
+static void ClearResolutionFunctionSlots(JSFunction* resolutionFun);
+static MOZ_MUST_USE bool RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj,
+ HandleValue reason);
+
+// ES2016, 25.4.1.3.1.
+static bool
+RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedFunction reject(cx, &args.callee().as<JSFunction>());
+ RootedValue reasonVal(cx, args.get(0));
+
+ // Steps 1-2.
+ RootedValue promiseVal(cx, reject->getExtendedSlot(RejectFunctionSlot_Promise));
+
+ // Steps 3-4.
+ // If the Promise isn't available anymore, it has been resolved and the
+ // reference to it removed to make it eligible for collection.
+ if (promiseVal.isUndefined()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ RootedObject promise(cx, &promiseVal.toObject());
+
+ // In some cases the Promise reference on the resolution function won't
+ // have been removed during resolution, so we need to check that here,
+ // too.
+ if (promise->is<PromiseObject>() &&
+ PromiseHasAnyFlag(promise->as<PromiseObject>(), PROMISE_FLAG_RESOLVED))
+ {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 5.
+ // Here, we only remove the Promise reference from the resolution
+ // functions. Actually marking it as fulfilled/rejected happens later.
+ ClearResolutionFunctionSlots(reject);
+
+ // Step 6.
+ bool result = RejectMaybeWrappedPromise(cx, promise, reasonVal);
+ if (result)
+ args.rval().setUndefined();
+ return result;
+}
+
+static MOZ_MUST_USE bool FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj,
+ HandleValue value_);
+
+static MOZ_MUST_USE bool EnqueuePromiseResolveThenableJob(JSContext* cx,
+ HandleValue promiseToResolve,
+ HandleValue thenable,
+ HandleValue thenVal);
+
+// ES2016, 25.4.1.3.2, steps 7-13.
+static MOZ_MUST_USE bool
+ResolvePromiseInternal(JSContext* cx, HandleObject promise, HandleValue resolutionVal)
+{
+ // Step 7.
+ if (!resolutionVal.isObject())
+ return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
+
+ RootedObject resolution(cx, &resolutionVal.toObject());
+
+ // Step 8.
+ RootedValue thenVal(cx);
+ bool status = GetProperty(cx, resolution, resolution, cx->names().then, &thenVal);
+
+ // Step 9.
+ if (!status) {
+ RootedValue error(cx);
+ if (!MaybeGetAndClearException(cx, &error))
+ return false;
+
+ return RejectMaybeWrappedPromise(cx, promise, error);
+ }
+
+ // Step 10 (implicit).
+
+ // Step 11.
+ if (!IsCallable(thenVal))
+ return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
+
+ // Step 12.
+ RootedValue promiseVal(cx, ObjectValue(*promise));
+ if (!EnqueuePromiseResolveThenableJob(cx, promiseVal, resolutionVal, thenVal))
+ return false;
+
+ // Step 13.
+ return true;
+}
+
+// ES2016, 25.4.1.3.2.
+static bool
+ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedFunction resolve(cx, &args.callee().as<JSFunction>());
+ RootedValue resolutionVal(cx, args.get(0));
+
+ // Steps 1-2.
+ RootedValue promiseVal(cx, resolve->getExtendedSlot(ResolveFunctionSlot_Promise));
+
+ // Steps 3-4.
+ // We use the reference to the reject function as a signal for whether
+ // the resolve or reject function was already called, at which point
+ // the references on each of the functions are cleared.
+ if (!resolve->getExtendedSlot(ResolveFunctionSlot_RejectFunction).isObject()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ RootedObject promise(cx, &promiseVal.toObject());
+
+ // In some cases the Promise reference on the resolution function won't
+ // have been removed during resolution, so we need to check that here,
+ // too.
+ if (promise->is<PromiseObject>() &&
+ PromiseHasAnyFlag(promise->as<PromiseObject>(), PROMISE_FLAG_RESOLVED))
+ {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 5.
+ // Here, we only remove the Promise reference from the resolution
+ // functions. Actually marking it as fulfilled/rejected happens later.
+ ClearResolutionFunctionSlots(resolve);
+
+ // Step 6.
+ if (resolutionVal == promiseVal) {
+ // Step 6.a.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF);
+ RootedValue selfResolutionError(cx);
+ bool status = GetAndClearException(cx, &selfResolutionError);
+ MOZ_ASSERT(status);
+
+ // Step 6.b.
+ status = RejectMaybeWrappedPromise(cx, promise, selfResolutionError);
+ if (status)
+ args.rval().setUndefined();
+ return status;
+ }
+
+ bool status = ResolvePromiseInternal(cx, promise, resolutionVal);
+ if (status)
+ args.rval().setUndefined();
+ return status;
+}
+
+static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Tells the embedding to enqueue a Promise reaction job, based on
+ * three parameters:
+ * reactionObj - The reaction record.
+ * handlerArg_ - The first and only argument to pass to the handler invoked by
+ * the job. This will be stored on the reaction record.
+ * targetState - The PromiseState this reaction job targets. This decides
+ * whether the onFulfilled or onRejected handler is called.
+ */
+MOZ_MUST_USE static bool
+EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj,
+ HandleValue handlerArg_, JS::PromiseState targetState)
+{
+ // The reaction might have been stored on a Promise from another
+ // compartment, which means it would've been wrapped in a CCW.
+ // To properly handle that case here, unwrap it and enter its
+ // compartment, where the job creation should take place anyway.
+ Rooted<PromiseReactionRecord*> reaction(cx);
+ RootedValue handlerArg(cx, handlerArg_);
+ mozilla::Maybe<AutoCompartment> ac;
+ if (!IsProxy(reactionObj)) {
+ MOZ_RELEASE_ASSERT(reactionObj->is<PromiseReactionRecord>());
+ reaction = &reactionObj->as<PromiseReactionRecord>();
+ } else {
+ if (JS_IsDeadWrapper(UncheckedUnwrap(reactionObj))) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
+ return false;
+ }
+ reaction = &UncheckedUnwrap(reactionObj)->as<PromiseReactionRecord>();
+ MOZ_RELEASE_ASSERT(reaction->is<PromiseReactionRecord>());
+ ac.emplace(cx, reaction);
+ if (!reaction->compartment()->wrap(cx, &handlerArg))
+ return false;
+ }
+
+ // Must not enqueue a reaction job more than once.
+ MOZ_ASSERT(reaction->targetState() == JS::PromiseState::Pending);
+
+ assertSameCompartment(cx, handlerArg);
+ reaction->setHandlerArg(handlerArg.get());
+
+ RootedValue reactionVal(cx, ObjectValue(*reaction));
+
+ reaction->setTargetState(targetState);
+ RootedValue handler(cx, reaction->handler());
+
+ // If we have a handler callback, we enter that handler's compartment so
+ // that the promise reaction job function is created in that compartment.
+ // That guarantees that the embedding ends up with the right entry global.
+ // This is relevant for some html APIs like fetch that derive information
+ // from said global.
+ mozilla::Maybe<AutoCompartment> ac2;
+ if (handler.isObject()) {
+ RootedObject handlerObj(cx, &handler.toObject());
+
+ // The unwrapping has to be unchecked because we specifically want to
+ // be able to use handlers with wrappers that would only allow calls.
+ // E.g., it's ok to have a handler from a chrome compartment in a
+ // reaction to a content compartment's Promise instance.
+ handlerObj = UncheckedUnwrap(handlerObj);
+ MOZ_ASSERT(handlerObj);
+ ac2.emplace(cx, handlerObj);
+
+ // We need to wrap the reaction to store it on the job function.
+ if (!cx->compartment()->wrap(cx, &reactionVal))
+ return false;
+ }
+
+ // Create the JS function to call when the job is triggered.
+ RootedAtom funName(cx, cx->names().empty);
+ RootedFunction job(cx, NewNativeFunction(cx, PromiseReactionJob, 0, funName,
+ gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
+ if (!job)
+ return false;
+
+ // Store the reaction on the reaction job.
+ job->setExtendedSlot(ReactionJobSlot_ReactionRecord, reactionVal);
+
+ // When using JS::AddPromiseReactions, no actual promise is created, so we
+ // might not have one here.
+ // Additionally, we might have an object here that isn't an instance of
+ // Promise. This can happen if content overrides the value of
+ // Promise[@@species] (or invokes Promise#then on a Promise subclass
+ // instance with a non-default @@species value on the constructor) with a
+ // function that returns objects that're not Promise (subclass) instances.
+ // In that case, we just pretend we didn't have an object in the first
+ // place.
+ // If after all this we do have an object, wrap it in case we entered the
+ // handler's compartment above, because we should pass objects from a
+ // single compartment to the enqueuePromiseJob callback.
+ RootedObject promise(cx, reaction->promise());
+ if (promise && promise->is<PromiseObject>()) {
+ if (!cx->compartment()->wrap(cx, &promise))
+ return false;
+ }
+
+ // Using objectFromIncumbentGlobal, we can derive the incumbent global by
+ // unwrapping and then getting the global. This is very convoluted, but
+ // much better than having to store the original global as a private value
+ // because we couldn't wrap it to store it as a normal JS value.
+ RootedObject global(cx);
+ RootedObject objectFromIncumbentGlobal(cx, reaction->incumbentGlobalObject());
+ if (objectFromIncumbentGlobal) {
+ objectFromIncumbentGlobal = CheckedUnwrap(objectFromIncumbentGlobal);
+ MOZ_ASSERT(objectFromIncumbentGlobal);
+ global = &objectFromIncumbentGlobal->global();
+ }
+
+ // Note: the global we pass here might be from a different compartment
+ // than job and promise. While it's somewhat unusual to pass objects
+ // from multiple compartments, in this case we specifically need the
+ // global to be unwrapped because wrapping and unwrapping aren't
+ // necessarily symmetric for globals.
+ return cx->runtime()->enqueuePromiseJob(cx, job, promise, global);
+}
+
+static MOZ_MUST_USE bool TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal,
+ JS::PromiseState state, HandleValue valueOrReason);
+
+// ES2016, Commoned-out implementation of 25.4.1.4. and 25.4.1.7.
+static MOZ_MUST_USE bool
+ResolvePromise(JSContext* cx, Handle<PromiseObject*> promise, HandleValue valueOrReason,
+ JS::PromiseState state)
+{
+ // Step 1.
+ MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
+ MOZ_ASSERT(state == JS::PromiseState::Fulfilled || state == JS::PromiseState::Rejected);
+
+ // Step 2.
+ // We only have one list of reactions for both resolution types. So
+ // instead of getting the right list of reactions, we determine the
+ // resolution type to retrieve the right information from the
+ // reaction records.
+ RootedValue reactionsVal(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult));
+
+ // Steps 3-5.
+ // The same slot is used for the reactions list and the result, so setting
+ // the result also removes the reactions list.
+ promise->setFixedSlot(PromiseSlot_ReactionsOrResult, valueOrReason);
+
+ // Step 6.
+ int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32();
+ flags |= PROMISE_FLAG_RESOLVED;
+ if (state == JS::PromiseState::Fulfilled)
+ flags |= PROMISE_FLAG_FULFILLED;
+ promise->setFixedSlot(PromiseSlot_Flags, Int32Value(flags));
+
+ // Also null out the resolve/reject functions so they can be GC'd.
+ promise->setFixedSlot(PromiseSlot_RejectFunction, UndefinedValue());
+
+ // Now that everything else is done, do the things the debugger needs.
+ // Step 7 of RejectPromise implemented in onSettled.
+ promise->onSettled(cx);
+
+ // Step 7 of FulfillPromise.
+ // Step 8 of RejectPromise.
+ if (reactionsVal.isObject())
+ return TriggerPromiseReactions(cx, reactionsVal, state, valueOrReason);
+
+ return true;
+}
+
+// ES2016, 25.4.1.4.
+static MOZ_MUST_USE bool
+FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue value_)
+{
+ Rooted<PromiseObject*> promise(cx);
+ RootedValue value(cx, value_);
+
+ mozilla::Maybe<AutoCompartment> ac;
+ if (!IsProxy(promiseObj)) {
+ promise = &promiseObj->as<PromiseObject>();
+ } else {
+ if (JS_IsDeadWrapper(UncheckedUnwrap(promiseObj))) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
+ return false;
+ }
+ promise = &UncheckedUnwrap(promiseObj)->as<PromiseObject>();
+ ac.emplace(cx, promise);
+ if (!promise->compartment()->wrap(cx, &value))
+ return false;
+ }
+
+ MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
+
+ return ResolvePromise(cx, promise, value, JS::PromiseState::Fulfilled);
+}
+
+static bool GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp);
+static bool PromiseConstructor(JSContext* cx, unsigned argc, Value* vp);
+static MOZ_MUST_USE PromiseObject* CreatePromiseObjectInternal(JSContext* cx,
+ HandleObject proto = nullptr,
+ bool protoIsWrapped = false,
+ bool informDebugger = true);
+
+enum GetCapabilitiesExecutorSlots {
+ GetCapabilitiesExecutorSlots_Resolve,
+ GetCapabilitiesExecutorSlots_Reject
+};
+
+static MOZ_MUST_USE PromiseObject*
+CreatePromiseObjectWithDefaultResolution(JSContext* cx)
+{
+ Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx));
+ if (!promise)
+ return nullptr;
+
+ AddPromiseFlags(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION |
+ PROMISE_FLAG_DEFAULT_REJECT_FUNCTION);
+ return promise;
+}
+
+// ES2016, 25.4.1.5.
+static MOZ_MUST_USE bool
+NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise,
+ MutableHandleObject resolve, MutableHandleObject reject,
+ bool canOmitResolutionFunctions)
+{
+ RootedValue cVal(cx, ObjectValue(*C));
+
+ // Steps 1-2.
+ if (!IsConstructor(C)) {
+ ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, -1, cVal, nullptr);
+ return false;
+ }
+
+ // If we'd call the original Promise constructor and know that the
+ // resolve/reject functions won't ever escape to content, we can skip
+ // creating and calling the executor function and instead return a Promise
+ // marked as having default resolve/reject functions.
+ //
+ // This can't be used in Promise.all and Promise.race because we have to
+ // pass the reject (and resolve, in the race case) function to thenables
+ // in the list passed to all/race, which (potentially) means exposing them
+ // to content.
+ if (canOmitResolutionFunctions && IsNativeFunction(cVal, PromiseConstructor)) {
+ promise.set(CreatePromiseObjectWithDefaultResolution(cx));
+ if (!promise)
+ return false;
+ return true;
+ }
+
+ // Step 3 (omitted).
+
+ // Step 4.
+ RootedAtom funName(cx, cx->names().empty);
+ RootedFunction executor(cx, NewNativeFunction(cx, GetCapabilitiesExecutor, 2, funName,
+ gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
+ if (!executor)
+ return false;
+
+ // Step 5 (omitted).
+
+ // Step 6.
+ FixedConstructArgs<1> cargs(cx);
+ cargs[0].setObject(*executor);
+ if (!Construct(cx, cVal, cargs, cVal, promise))
+ return false;
+
+ // Step 7.
+ RootedValue resolveVal(cx, executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve));
+ if (!IsCallable(resolveVal)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE);
+ return false;
+ }
+
+ // Step 8.
+ RootedValue rejectVal(cx, executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject));
+ if (!IsCallable(rejectVal)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE);
+ return false;
+ }
+
+ // Step 9 (well, the equivalent for all of promiseCapabilities' fields.)
+ resolve.set(&resolveVal.toObject());
+ reject.set(&rejectVal.toObject());
+
+ // Step 10.
+ return true;
+}
+
+// ES2016, 25.4.1.5.1.
+static bool
+GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedFunction F(cx, &args.callee().as<JSFunction>());
+
+ // Steps 1-2 (implicit).
+
+ // Steps 3-4.
+ if (!F->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve).isUndefined() ||
+ !F->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject).isUndefined())
+ {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY);
+ return false;
+ }
+
+ // Step 5.
+ F->setExtendedSlot(GetCapabilitiesExecutorSlots_Resolve, args.get(0));
+
+ // Step 6.
+ F->setExtendedSlot(GetCapabilitiesExecutorSlots_Reject, args.get(1));
+
+ // Step 7.
+ args.rval().setUndefined();
+ return true;
+}
+
+// ES2016, 25.4.1.7.
+static MOZ_MUST_USE bool
+RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue reason_)
+{
+ Rooted<PromiseObject*> promise(cx);
+ RootedValue reason(cx, reason_);
+
+ mozilla::Maybe<AutoCompartment> ac;
+ if (!IsProxy(promiseObj)) {
+ promise = &promiseObj->as<PromiseObject>();
+ } else {
+ if (JS_IsDeadWrapper(UncheckedUnwrap(promiseObj))) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
+ return false;
+ }
+ promise = &UncheckedUnwrap(promiseObj)->as<PromiseObject>();
+ ac.emplace(cx, promise);
+
+ // The rejection reason might've been created in a compartment with higher
+ // privileges than the Promise's. In that case, object-type rejection
+ // values might be wrapped into a wrapper that throws whenever the
+ // Promise's reaction handler wants to do anything useful with it. To
+ // avoid that situation, we synthesize a generic error that doesn't
+ // expose any privileged information but can safely be used in the
+ // rejection handler.
+ if (!promise->compartment()->wrap(cx, &reason))
+ return false;
+ if (reason.isObject() && !CheckedUnwrap(&reason.toObject())) {
+ // Async stacks are only properly adopted if there's at least one
+ // interpreter frame active right now. If a thenable job with a
+ // throwing `then` function got us here, that'll not be the case,
+ // so we add one by throwing the error from self-hosted code.
+ FixedInvokeArgs<1> getErrorArgs(cx);
+ getErrorArgs[0].set(Int32Value(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON));
+ if (!CallSelfHostedFunction(cx, "GetInternalError", reason, getErrorArgs, &reason))
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
+
+ return ResolvePromise(cx, promise, reason, JS::PromiseState::Rejected);
+}
+
+// ES2016, 25.4.1.8.
+static MOZ_MUST_USE bool
+TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal, JS::PromiseState state,
+ HandleValue valueOrReason)
+{
+ RootedObject reactions(cx, &reactionsVal.toObject());
+ RootedObject reaction(cx);
+
+ if (reactions->is<PromiseReactionRecord>() || IsWrapper(reactions))
+ return EnqueuePromiseReactionJob(cx, reactions, valueOrReason, state);
+
+ RootedNativeObject reactionsList(cx, &reactions->as<NativeObject>());
+ size_t reactionsCount = reactionsList->getDenseInitializedLength();
+ MOZ_ASSERT(reactionsCount > 1, "Reactions list should be created lazily");
+
+ for (size_t i = 0; i < reactionsCount; i++) {
+ reaction = &reactionsList->getDenseElement(i).toObject();
+ if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state))
+ return false;
+ }
+
+ return true;
+}
+
+static MOZ_MUST_USE bool
+AwaitPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord*> reaction,
+ MutableHandleValue rval)
+{
+ MOZ_ASSERT(reaction->isAwait());
+
+ RootedValue handlerVal(cx, reaction->handler());
+ RootedValue argument(cx, reaction->handlerArg());
+ Rooted<PromiseObject*> resultPromise(cx, &reaction->promise()->as<PromiseObject>());
+ RootedValue generatorVal(cx, resultPromise->getFixedSlot(PromiseSlot_AwaitGenerator));
+
+ int32_t handlerNum = int32_t(handlerVal.toNumber());
+ MOZ_ASSERT(handlerNum == PROMISE_HANDLER_AWAIT_FULFILLED
+ || handlerNum == PROMISE_HANDLER_AWAIT_REJECTED);
+
+ // Await's handlers don't return a value, nor throw exception.
+ // They fail only on OOM.
+ if (handlerNum == PROMISE_HANDLER_AWAIT_FULFILLED) {
+ if (!AsyncFunctionAwaitedFulfilled(cx, resultPromise, generatorVal, argument))
+ return false;
+ } else {
+ if (!AsyncFunctionAwaitedRejected(cx, resultPromise, generatorVal, argument))
+ return false;
+ }
+
+ rval.setUndefined();
+ return true;
+}
+
+// ES2016, 25.4.2.1.
+/**
+ * Callback triggering the fulfill/reject reaction for a resolved Promise,
+ * to be invoked by the embedding during its processing of the Promise job
+ * queue.
+ *
+ * See http://www.ecma-international.org/ecma-262/7.0/index.html#sec-jobs-and-job-queues
+ *
+ * A PromiseReactionJob is set as the native function of an extended
+ * JSFunction object, with all information required for the job's
+ * execution stored in in a reaction record in its first extended slot.
+ */
+static bool
+PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedFunction job(cx, &args.callee().as<JSFunction>());
+
+ RootedObject reactionObj(cx, &job->getExtendedSlot(ReactionJobSlot_ReactionRecord).toObject());
+
+ // To ensure that the embedding ends up with the right entry global, we're
+ // guaranteeing that the reaction job function gets created in the same
+ // compartment as the handler function. That's not necessarily the global
+ // that the job was triggered from, though.
+ // We can find the triggering global via the job's reaction record. To go
+ // back, we check if the reaction is a wrapper and if so, unwrap it and
+ // enter its compartment.
+ mozilla::Maybe<AutoCompartment> ac;
+ if (!IsProxy(reactionObj)) {
+ MOZ_RELEASE_ASSERT(reactionObj->is<PromiseReactionRecord>());
+ } else {
+ reactionObj = UncheckedUnwrap(reactionObj);
+ if (JS_IsDeadWrapper(reactionObj)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
+ return false;
+ }
+ MOZ_RELEASE_ASSERT(reactionObj->is<PromiseReactionRecord>());
+ ac.emplace(cx, reactionObj);
+ }
+
+ // Steps 1-2.
+ Rooted<PromiseReactionRecord*> reaction(cx, &reactionObj->as<PromiseReactionRecord>());
+ if (reaction->isAwait())
+ return AwaitPromiseReactionJob(cx, reaction, args.rval());
+
+ // Step 3.
+ RootedValue handlerVal(cx, reaction->handler());
+
+ RootedValue argument(cx, reaction->handlerArg());
+
+ RootedValue handlerResult(cx);
+ ResolutionMode resolutionMode = ResolveMode;
+
+ // Steps 4-6.
+ if (handlerVal.isNumber()) {
+ int32_t handlerNum = int32_t(handlerVal.toNumber());
+
+ // Step 4.
+ if (handlerNum == PROMISE_HANDLER_IDENTITY) {
+ handlerResult = argument;
+ } else {
+ // Step 5.
+ MOZ_ASSERT(handlerNum == PROMISE_HANDLER_THROWER);
+ resolutionMode = RejectMode;
+ handlerResult = argument;
+ }
+ } else {
+ // Step 6.
+ FixedInvokeArgs<1> args2(cx);
+ args2[0].set(argument);
+ if (!Call(cx, handlerVal, UndefinedHandleValue, args2, &handlerResult)) {
+ resolutionMode = RejectMode;
+ if (!MaybeGetAndClearException(cx, &handlerResult))
+ return false;
+ }
+ }
+
+ // Steps 7-9.
+ size_t hookSlot = resolutionMode == RejectMode
+ ? ReactionRecordSlot_Reject
+ : ReactionRecordSlot_Resolve;
+ RootedObject callee(cx, reaction->getFixedSlot(hookSlot).toObjectOrNull());
+ RootedObject promiseObj(cx, reaction->promise());
+ if (!RunResolutionFunction(cx, callee, handlerResult, resolutionMode, promiseObj))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+
+// ES2016, 25.4.2.2.
+/**
+ * Callback for resolving a thenable, to be invoked by the embedding during
+ * its processing of the Promise job queue.
+ *
+ * See http://www.ecma-international.org/ecma-262/7.0/index.html#sec-jobs-and-job-queues
+ *
+ * A PromiseResolveThenableJob is set as the native function of an extended
+ * JSFunction object, with all information required for the job's
+ * execution stored in the function's extended slots.
+ *
+ * Usage of the function's extended slots is as follows:
+ * ThenableJobSlot_Handler: The handler to use as the Promise reaction.
+ * This can be PROMISE_HANDLER_IDENTITY,
+ * PROMISE_HANDLER_THROWER, or a callable. In the
+ * latter case, it's guaranteed to be an object
+ * from the same compartment as the
+ * PromiseReactionJob.
+ * ThenableJobSlot_JobData: JobData - a, potentially CCW-wrapped, dense list
+ * containing data required for proper execution of
+ * the reaction.
+ *
+ * The JobData list has the following entries:
+ * ThenableJobDataSlot_Promise: The Promise to resolve using the given
+ * thenable.
+ * ThenableJobDataSlot_Thenable: The thenable to use as the receiver when
+ * calling the `then` function.
+ */
+static bool
+PromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedFunction job(cx, &args.callee().as<JSFunction>());
+ RootedValue then(cx, job->getExtendedSlot(ThenableJobSlot_Handler));
+ MOZ_ASSERT(!IsWrapper(&then.toObject()));
+ RootedNativeObject jobArgs(cx, &job->getExtendedSlot(ThenableJobSlot_JobData)
+ .toObject().as<NativeObject>());
+
+ RootedValue promise(cx, jobArgs->getDenseElement(ThenableJobDataIndex_Promise));
+ RootedValue thenable(cx, jobArgs->getDenseElement(ThenableJobDataIndex_Thenable));
+
+ // Step 1.
+ RootedValue resolveVal(cx);
+ RootedValue rejectVal(cx);
+ if (!CreateResolvingFunctions(cx, promise, &resolveVal, &rejectVal))
+ return false;
+
+ // Step 2.
+ FixedInvokeArgs<2> args2(cx);
+ args2[0].set(resolveVal);
+ args2[1].set(rejectVal);
+
+ RootedValue rval(cx);
+
+ // In difference to the usual pattern, we return immediately on success.
+ if (Call(cx, then, thenable, args2, &rval))
+ return true;
+
+ if (!MaybeGetAndClearException(cx, &rval))
+ return false;
+
+ FixedInvokeArgs<1> rejectArgs(cx);
+ rejectArgs[0].set(rval);
+
+ return Call(cx, rejectVal, UndefinedHandleValue, rejectArgs, &rval);
+}
+
+/**
+ * Tells the embedding to enqueue a Promise resolve thenable job, based on
+ * three parameters:
+ * promiseToResolve_ - The promise to resolve, obviously.
+ * thenable_ - The thenable to resolve the Promise with.
+ * thenVal - The `then` function to invoke with the `thenable` as the receiver.
+ */
+static MOZ_MUST_USE bool
+EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve_,
+ HandleValue thenable_, HandleValue thenVal)
+{
+ // Need to re-root these to enable wrapping them below.
+ RootedValue promiseToResolve(cx, promiseToResolve_);
+ RootedValue thenable(cx, thenable_);
+
+ // We enter the `then` callable's compartment so that the job function is
+ // created in that compartment.
+ // That guarantees that the embedding ends up with the right entry global.
+ // This is relevant for some html APIs like fetch that derive information
+ // from said global.
+ RootedObject then(cx, CheckedUnwrap(&thenVal.toObject()));
+ AutoCompartment ac(cx, then);
+
+ RootedAtom funName(cx, cx->names().empty);
+ RootedFunction job(cx, NewNativeFunction(cx, PromiseResolveThenableJob, 0, funName,
+ gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
+ if (!job)
+ return false;
+
+ // Store the `then` function on the callback.
+ job->setExtendedSlot(ThenableJobSlot_Handler, ObjectValue(*then));
+
+ // Create a dense array to hold the data needed for the reaction job to
+ // work.
+ // See the doc comment for PromiseResolveThenableJob for the layout.
+ RootedArrayObject data(cx, NewDenseFullyAllocatedArray(cx, ThenableJobDataLength));
+ if (!data ||
+ data->ensureDenseElements(cx, 0, ThenableJobDataLength) != DenseElementResult::Success)
+ {
+ return false;
+ }
+
+ // Wrap and set the `promiseToResolve` argument.
+ if (!cx->compartment()->wrap(cx, &promiseToResolve))
+ return false;
+ data->setDenseElement(ThenableJobDataIndex_Promise, promiseToResolve);
+ // At this point the promise is guaranteed to be wrapped into the job's
+ // compartment.
+ RootedObject promise(cx, &promiseToResolve.toObject());
+
+ // Wrap and set the `thenable` argument.
+ MOZ_ASSERT(thenable.isObject());
+ if (!cx->compartment()->wrap(cx, &thenable))
+ return false;
+ data->setDenseElement(ThenableJobDataIndex_Thenable, thenable);
+
+ // Store the data array on the reaction job.
+ job->setExtendedSlot(ThenableJobSlot_JobData, ObjectValue(*data));
+
+ RootedObject incumbentGlobal(cx, cx->runtime()->getIncumbentGlobal(cx));
+ return cx->runtime()->enqueuePromiseJob(cx, job, promise, incumbentGlobal);
+}
+
+static MOZ_MUST_USE bool
+AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled,
+ HandleValue onRejected, HandleObject dependentPromise,
+ HandleObject resolve, HandleObject reject, HandleObject incumbentGlobal);
+
+static MOZ_MUST_USE bool
+AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise,
+ Handle<PromiseReactionRecord*> reaction);
+
+static MOZ_MUST_USE bool BlockOnPromise(JSContext* cx, HandleValue promise,
+ HandleObject blockedPromise,
+ HandleValue onFulfilled, HandleValue onRejected);
+
+static JSFunction*
+GetResolveFunctionFromReject(JSFunction* reject)
+{
+ MOZ_ASSERT(reject->maybeNative() == RejectPromiseFunction);
+ Value resolveFunVal = reject->getExtendedSlot(RejectFunctionSlot_ResolveFunction);
+ if (IsNativeFunction(resolveFunVal, ResolvePromiseFunction))
+ return &resolveFunVal.toObject().as<JSFunction>();
+
+ PromiseAllDataHolder* resolveFunObj = &resolveFunVal.toObject().as<PromiseAllDataHolder>();
+ return &resolveFunObj->resolveObj()->as<JSFunction>();
+}
+
+static JSFunction*
+GetResolveFunctionFromPromise(PromiseObject* promise)
+{
+ Value rejectFunVal = promise->getFixedSlot(PromiseSlot_RejectFunction);
+ if (rejectFunVal.isUndefined())
+ return nullptr;
+ JSObject* rejectFunObj = &rejectFunVal.toObject();
+
+ // We can safely unwrap it because all we want is to get the resolve
+ // function.
+ if (IsWrapper(rejectFunObj))
+ rejectFunObj = UncheckedUnwrap(rejectFunObj);
+
+ if (!rejectFunObj->is<JSFunction>())
+ return nullptr;
+
+ JSFunction* rejectFun = &rejectFunObj->as<JSFunction>();
+
+ // Only the original RejectPromiseFunction has a reference to the resolve
+ // function.
+ if (rejectFun->maybeNative() != &RejectPromiseFunction)
+ return nullptr;
+
+ return GetResolveFunctionFromReject(rejectFun);
+}
+
+static void
+ClearResolutionFunctionSlots(JSFunction* resolutionFun)
+{
+ JSFunction* resolve;
+ JSFunction* reject;
+ if (resolutionFun->maybeNative() == ResolvePromiseFunction) {
+ resolve = resolutionFun;
+ reject = &resolutionFun->getExtendedSlot(ResolveFunctionSlot_RejectFunction)
+ .toObject().as<JSFunction>();
+ } else {
+ resolve = GetResolveFunctionFromReject(resolutionFun);
+ reject = resolutionFun;
+ }
+
+ resolve->setExtendedSlot(ResolveFunctionSlot_Promise, UndefinedValue());
+ resolve->setExtendedSlot(ResolveFunctionSlot_RejectFunction, UndefinedValue());
+
+ reject->setExtendedSlot(RejectFunctionSlot_Promise, UndefinedValue());
+ reject->setExtendedSlot(RejectFunctionSlot_ResolveFunction, UndefinedValue());
+}
+
+// ES2016, 25.4.3.1. steps 3-7.
+static MOZ_MUST_USE PromiseObject*
+CreatePromiseObjectInternal(JSContext* cx, HandleObject proto /* = nullptr */,
+ bool protoIsWrapped /* = false */, bool informDebugger /* = true */)
+{
+ // Step 3.
+ Rooted<PromiseObject*> promise(cx);
+ // Enter the unwrapped proto's compartment, if that's different from
+ // the current one.
+ // All state stored in a Promise's fixed slots must be created in the
+ // same compartment, so we get all of that out of the way here.
+ // (Except for the resolution functions, which are created below.)
+ mozilla::Maybe<AutoCompartment> ac;
+ if (protoIsWrapped)
+ ac.emplace(cx, proto);
+
+ promise = NewObjectWithClassProto<PromiseObject>(cx, proto);
+ if (!promise)
+ return nullptr;
+
+ // Step 4.
+ promise->setFixedSlot(PromiseSlot_Flags, Int32Value(0));
+
+ // Steps 5-6.
+ // Omitted, we allocate our single list of reaction records lazily.
+
+ // Step 7.
+ // Implicit, the handled flag is unset by default.
+
+ // Store an allocation stack so we can later figure out what the
+ // control flow was for some unexpected results. Frightfully expensive,
+ // but oh well.
+ RootedObject stack(cx);
+ if (cx->options().asyncStack() || cx->compartment()->isDebuggee()) {
+ if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames())))
+ return nullptr;
+ }
+ promise->setFixedSlot(PromiseSlot_AllocationSite, ObjectOrNullValue(stack));
+ promise->setFixedSlot(PromiseSlot_AllocationTime, DoubleValue(MillisecondsSinceStartup()));
+
+ // Let the Debugger know about this Promise.
+ if (informDebugger)
+ JS::dbg::onNewPromise(cx, promise);
+
+ return promise;
+}
+
+// ES2016, 25.4.3.1.
+static bool
+PromiseConstructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (!ThrowIfNotConstructing(cx, args, "Promise"))
+ return false;
+
+ // Step 2.
+ RootedValue executorVal(cx, args.get(0));
+ if (!IsCallable(executorVal))
+ return ReportIsNotFunction(cx, executorVal);
+ RootedObject executor(cx, &executorVal.toObject());
+
+ // Steps 3-10.
+ RootedObject newTarget(cx, &args.newTarget().toObject());
+ RootedObject originalNewTarget(cx, newTarget);
+ bool needsWrapping = false;
+
+ // If the constructor is called via an Xray wrapper, then the newTarget
+ // hasn't been unwrapped. We want that because, while the actual instance
+ // should be created in the target compartment, the constructor's code
+ // should run in the wrapper's compartment.
+ //
+ // This is so that the resolve and reject callbacks get created in the
+ // wrapper's compartment, which is required for code in that compartment
+ // to freely interact with it, and, e.g., pass objects as arguments, which
+ // it wouldn't be able to if the callbacks were themselves wrapped in Xray
+ // wrappers.
+ //
+ // At the same time, just creating the Promise itself in the wrapper's
+ // compartment wouldn't be helpful: if the wrapper forbids interactions
+ // with objects except for specific actions, such as calling them, then
+ // the code we want to expose it to can't actually treat it as a Promise:
+ // calling .then on it would throw, for example.
+ //
+ // Another scenario where it's important to create the Promise in a
+ // different compartment from the resolution functions is when we want to
+ // give non-privileged code a Promise resolved with the result of a
+ // Promise from privileged code; as a return value of a JS-implemented
+ // API, say. If the resolution functions were unprivileged, then resolving
+ // with a privileged Promise would cause `resolve` to attempt accessing
+ // .then on the passed Promise, which would throw an exception, so we'd
+ // just end up with a rejected Promise. Really, we want to chain the two
+ // Promises, with the unprivileged one resolved with the resolution of the
+ // privileged one.
+ if (IsWrapper(newTarget)) {
+ newTarget = CheckedUnwrap(newTarget);
+ MOZ_ASSERT(newTarget);
+ MOZ_ASSERT(newTarget != originalNewTarget);
+ {
+ AutoCompartment ac(cx, newTarget);
+ RootedObject promiseCtor(cx);
+ if (!GetBuiltinConstructor(cx, JSProto_Promise, &promiseCtor))
+ return false;
+
+ // Promise subclasses don't get the special Xray treatment, so
+ // we only need to do the complex wrapping and unwrapping scheme
+ // described above for instances of Promise itself.
+ if (newTarget == promiseCtor)
+ needsWrapping = true;
+ }
+ }
+
+ RootedObject proto(cx);
+ if (!GetPrototypeFromConstructor(cx, needsWrapping ? newTarget : originalNewTarget, &proto))
+ return false;
+ if (needsWrapping && !cx->compartment()->wrap(cx, &proto))
+ return false;
+ Rooted<PromiseObject*> promise(cx, PromiseObject::create(cx, executor, proto, needsWrapping));
+ if (!promise)
+ return false;
+
+ // Step 11.
+ args.rval().setObject(*promise);
+ if (needsWrapping)
+ return cx->compartment()->wrap(cx, args.rval());
+ return true;
+}
+
+// ES2016, 25.4.3.1. steps 3-11.
+/* static */ PromiseObject*
+PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /* = nullptr */,
+ bool needsWrapping /* = false */)
+{
+ MOZ_ASSERT(executor->isCallable());
+
+ RootedObject usedProto(cx, proto);
+ // If the proto is wrapped, that means the current function is running
+ // with a different compartment active from the one the Promise instance
+ // is to be created in.
+ // See the comment in PromiseConstructor for details.
+ if (needsWrapping) {
+ MOZ_ASSERT(proto);
+ usedProto = CheckedUnwrap(proto);
+ if (!usedProto)
+ return nullptr;
+ }
+
+
+ // Steps 3-7.
+ Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx, usedProto, needsWrapping,
+ false));
+ if (!promise)
+ return nullptr;
+
+ RootedValue promiseVal(cx, ObjectValue(*promise));
+ if (needsWrapping && !cx->compartment()->wrap(cx, &promiseVal))
+ return nullptr;
+
+ // Step 8.
+ // The resolving functions are created in the compartment active when the
+ // (maybe wrapped) Promise constructor was called. They contain checks and
+ // can unwrap the Promise if required.
+ RootedValue resolveVal(cx);
+ RootedValue rejectVal(cx);
+ if (!CreateResolvingFunctions(cx, promiseVal, &resolveVal, &rejectVal))
+ return nullptr;
+
+ // Need to wrap the resolution functions before storing them on the Promise.
+ if (needsWrapping) {
+ AutoCompartment ac(cx, promise);
+ RootedValue wrappedRejectVal(cx, rejectVal);
+ if (!cx->compartment()->wrap(cx, &wrappedRejectVal))
+ return nullptr;
+ promise->setFixedSlot(PromiseSlot_RejectFunction, wrappedRejectVal);
+ } else {
+ promise->setFixedSlot(PromiseSlot_RejectFunction, rejectVal);
+ }
+
+ // Step 9.
+ bool success;
+ {
+ FixedInvokeArgs<2> args(cx);
+
+ args[0].set(resolveVal);
+ args[1].set(rejectVal);
+
+ RootedValue calleeOrRval(cx, ObjectValue(*executor));
+ success = Call(cx, calleeOrRval, UndefinedHandleValue, args, &calleeOrRval);
+ }
+
+ // Step 10.
+ if (!success) {
+ RootedValue exceptionVal(cx);
+ if (!MaybeGetAndClearException(cx, &exceptionVal))
+ return nullptr;
+
+ FixedInvokeArgs<1> args(cx);
+
+ args[0].set(exceptionVal);
+
+ // |rejectVal| is unused after this, so we can safely write to it.
+ if (!Call(cx, rejectVal, UndefinedHandleValue, args, &rejectVal))
+ return nullptr;
+ }
+
+ // Let the Debugger know about this Promise.
+ JS::dbg::onNewPromise(cx, promise);
+
+ // Step 11.
+ return promise;
+}
+
+static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator,
+ HandleObject C, HandleObject promiseObj,
+ HandleObject resolve, HandleObject reject);
+
+// ES2016, 25.4.4.1.
+static bool
+Promise_static_all(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedValue iterable(cx, args.get(0));
+
+ // Step 2 (reordered).
+ RootedValue CVal(cx, args.thisv());
+ if (!CVal.isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
+ "Receiver of Promise.all call");
+ return false;
+ }
+
+ // Step 1.
+ RootedObject C(cx, &CVal.toObject());
+
+ // Step 3.
+ RootedObject resultPromise(cx);
+ RootedObject resolve(cx);
+ RootedObject reject(cx);
+ if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false))
+ return false;
+
+ // Steps 4-5.
+ JS::ForOfIterator iter(cx);
+ if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable))
+ return AbruptRejectPromise(cx, args, resultPromise, reject);
+
+ if (!iter.valueIsIterable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
+ "Argument of Promise.all");
+ return AbruptRejectPromise(cx, args, resultPromise, reject);
+ }
+
+ // Step 6 (implicit).
+
+ // Step 7.
+ bool result = PerformPromiseAll(cx, iter, C, resultPromise, resolve, reject);
+
+ // Step 8.
+ if (!result) {
+ // Step 8.a.
+ // TODO: implement iterator closing.
+
+ // Step 8.b.
+ return AbruptRejectPromise(cx, args, resultPromise, reject);
+ }
+
+ // Step 9.
+ args.rval().setObject(*resultPromise);
+ return true;
+}
+
+static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle<PromiseObject*> promise,
+ HandleValue onFulfilled_, HandleValue onRejected_,
+ HandleObject resultPromise,
+ HandleObject resolve, HandleObject reject);
+
+static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp);
+
+// Unforgeable version of ES2016, 25.4.4.1.
+MOZ_MUST_USE JSObject*
+js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises)
+{
+#ifdef DEBUG
+ for (size_t i = 0, len = promises.length(); i < len; i++) {
+ JSObject* obj = promises[i];
+ assertSameCompartment(cx, obj);
+ MOZ_ASSERT(UncheckedUnwrap(obj)->is<PromiseObject>());
+ }
+#endif
+
+ // Step 1.
+ RootedObject C(cx, GlobalObject::getOrCreatePromiseConstructor(cx, cx->global()));
+ if (!C)
+ return nullptr;
+
+ // Step 2 (omitted).
+
+ // Step 3.
+ RootedObject resultPromise(cx);
+ RootedObject resolve(cx);
+ RootedObject reject(cx);
+ if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false))
+ return nullptr;
+
+ // Steps 4-6 (omitted).
+
+ // Step 7.
+ // Implemented as an inlined, simplied version of ES2016 25.4.4.1.1, PerformPromiseAll.
+ {
+ uint32_t promiseCount = promises.length();
+ // Sub-steps 1-2 (omitted).
+
+ // Sub-step 3.
+ RootedNativeObject valuesArray(cx, NewDenseFullyAllocatedArray(cx, promiseCount));
+ if (!valuesArray)
+ return nullptr;
+ if (valuesArray->ensureDenseElements(cx, 0, promiseCount) != DenseElementResult::Success)
+ return nullptr;
+
+ // Sub-step 4.
+ // Create our data holder that holds all the things shared across
+ // every step of the iterator. In particular, this holds the
+ // remainingElementsCount (as an integer reserved slot), the array of
+ // values, and the resolve function from our PromiseCapability.
+ RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray));
+ Rooted<PromiseAllDataHolder*> dataHolder(cx, NewPromiseAllDataHolder(cx, resultPromise,
+ valuesArrayVal,
+ resolve));
+ if (!dataHolder)
+ return nullptr;
+ RootedValue dataHolderVal(cx, ObjectValue(*dataHolder));
+
+ // Sub-step 5 (inline in loop-header below).
+
+ // Sub-step 6.
+ for (uint32_t index = 0; index < promiseCount; index++) {
+ // Steps a-c (omitted).
+ // Step d (implemented after the loop).
+ // Steps e-g (omitted).
+
+ // Step h.
+ valuesArray->setDenseElement(index, UndefinedHandleValue);
+
+ // Step i, vastly simplified.
+ RootedObject nextPromiseObj(cx, promises[index]);
+
+ // Step j.
+ RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllResolveElementFunction,
+ 1, nullptr,
+ gc::AllocKind::FUNCTION_EXTENDED,
+ GenericObject));
+ if (!resolveFunc)
+ return nullptr;
+
+ // Steps k-o.
+ resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal);
+ resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex,
+ Int32Value(index));
+
+ // Step p.
+ dataHolder->increaseRemainingCount();
+
+ // Step q, very roughly.
+ RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc));
+ RootedValue rejectFunVal(cx, ObjectValue(*reject));
+ Rooted<PromiseObject*> nextPromise(cx);
+
+ // GetWaitForAllPromise is used internally only and must not
+ // trigger content-observable effects when registering a reaction.
+ // It's also meant to work on wrapped Promises, potentially from
+ // compartments with principals inaccessible from the current
+ // compartment. To make that work, it unwraps promises with
+ // UncheckedUnwrap,
+ nextPromise = &UncheckedUnwrap(nextPromiseObj)->as<PromiseObject>();
+
+ if (!PerformPromiseThen(cx, nextPromise, resolveFunVal, rejectFunVal,
+ resultPromise, nullptr, nullptr))
+ {
+ return nullptr;
+ }
+
+ // Step r (inline in loop-header).
+ }
+
+ // Sub-step d.i (implicit).
+ // Sub-step d.ii.
+ int32_t remainingCount = dataHolder->decreaseRemainingCount();
+
+ // Sub-step d.iii-iv.
+ if (remainingCount == 0) {
+ RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray));
+ if (!ResolvePromiseInternal(cx, resultPromise, valuesArrayVal))
+ return nullptr;
+ }
+ }
+
+ // Step 8 (omitted).
+
+ // Step 9.
+ return resultPromise;
+}
+
+static MOZ_MUST_USE bool
+RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue result,
+ ResolutionMode mode, HandleObject promiseObj)
+{
+ // The absence of a resolve/reject function can mean that, as an
+ // optimization, those weren't created. In that case, a flag is set on
+ // the Promise object. There are also reactions where the Promise
+ // itself is missing. For those, there's nothing left to do here.
+ assertSameCompartment(cx, resolutionFun);
+ assertSameCompartment(cx, result);
+ assertSameCompartment(cx, promiseObj);
+ if (resolutionFun) {
+ RootedValue calleeOrRval(cx, ObjectValue(*resolutionFun));
+ FixedInvokeArgs<1> resolveArgs(cx);
+ resolveArgs[0].set(result);
+ return Call(cx, calleeOrRval, UndefinedHandleValue, resolveArgs, &calleeOrRval);
+ }
+
+ if (!promiseObj)
+ return true;
+
+ Rooted<PromiseObject*> promise(cx, &promiseObj->as<PromiseObject>());
+ if (promise->state() != JS::PromiseState::Pending)
+ return true;
+
+
+ if (mode == ResolveMode) {
+ if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION))
+ return true;
+ return ResolvePromiseInternal(cx, promise, result);
+ }
+
+ if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_REJECT_FUNCTION))
+ return true;
+ return RejectMaybeWrappedPromise(cx, promiseObj, result);
+
+}
+
+// ES2016, 25.4.4.1.1.
+static MOZ_MUST_USE bool
+PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
+ HandleObject promiseObj, HandleObject resolve, HandleObject reject)
+{
+ RootedObject unwrappedPromiseObj(cx);
+ if (IsWrapper(promiseObj)) {
+ unwrappedPromiseObj = CheckedUnwrap(promiseObj);
+ MOZ_ASSERT(unwrappedPromiseObj);
+ }
+
+ // Step 1.
+ MOZ_ASSERT(C->isConstructor());
+ RootedValue CVal(cx, ObjectValue(*C));
+
+ // Step 2 (omitted).
+
+ // Step 3.
+ // We have to be very careful about which compartments we create things in
+ // here. In particular, we have to maintain the invariant that anything
+ // stored in a reserved slot is same-compartment with the object whose
+ // reserved slot it's in. But we want to create the values array in the
+ // Promise's compartment, because that array can get exposed to
+ // code that has access to the Promise (in particular code from
+ // that compartment), and that should work, even if the Promise
+ // compartment is less-privileged than our caller compartment.
+ //
+ // So the plan is as follows: Create the values array in the promise
+ // compartment. Create the PromiseAllResolveElement function
+ // and the data holder in our current compartment. Store a
+ // cross-compartment wrapper to the values array in the holder. This
+ // should be OK because the only things we hand the
+ // PromiseAllResolveElement function to are the "then" calls we do and in
+ // the case when the Promise's compartment is not the current compartment
+ // those are happening over Xrays anyway, which means they get the
+ // canonical "then" function and content can't see our
+ // PromiseAllResolveElement.
+ RootedObject valuesArray(cx);
+ if (unwrappedPromiseObj) {
+ JSAutoCompartment ac(cx, unwrappedPromiseObj);
+ valuesArray = NewDenseFullyAllocatedArray(cx, 0);
+ } else {
+ valuesArray = NewDenseFullyAllocatedArray(cx, 0);
+ }
+ if (!valuesArray)
+ return false;
+
+ RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray));
+ if (!cx->compartment()->wrap(cx, &valuesArrayVal))
+ return false;
+
+ // Step 4.
+ // Create our data holder that holds all the things shared across
+ // every step of the iterator. In particular, this holds the
+ // remainingElementsCount (as an integer reserved slot), the array of
+ // values, and the resolve function from our PromiseCapability.
+ Rooted<PromiseAllDataHolder*> dataHolder(cx, NewPromiseAllDataHolder(cx, promiseObj,
+ valuesArrayVal, resolve));
+ if (!dataHolder)
+ return false;
+ RootedValue dataHolderVal(cx, ObjectValue(*dataHolder));
+
+ // Step 5.
+ uint32_t index = 0;
+
+ // Step 6.
+ RootedValue nextValue(cx);
+ RootedId indexId(cx);
+ RootedValue rejectFunVal(cx, ObjectOrNullValue(reject));
+
+ while (true) {
+ bool done;
+ // Steps a, b, c, e, f, g.
+ if (!iterator.next(&nextValue, &done))
+ return false;
+
+ // Step d.
+ if (done) {
+ // Step d.i (implicit).
+ // Step d.ii.
+ int32_t remainingCount = dataHolder->decreaseRemainingCount();
+
+ // Steps d.iii-iv.
+ if (remainingCount == 0) {
+ if (resolve) {
+ return RunResolutionFunction(cx, resolve, valuesArrayVal, ResolveMode,
+ promiseObj);
+ }
+ return ResolvePromiseInternal(cx, promiseObj, valuesArrayVal);
+ }
+
+ // We're all set for now!
+ return true;
+ }
+
+ // Step h.
+ { // Scope for the JSAutoCompartment we need to work with valuesArray. We
+ // mostly do this for performance; we could go ahead and do the define via
+ // a cross-compartment proxy instead...
+ JSAutoCompartment ac(cx, valuesArray);
+ indexId = INT_TO_JSID(index);
+ if (!DefineProperty(cx, valuesArray, indexId, UndefinedHandleValue))
+ return false;
+ }
+
+ // Step i.
+ // Sadly, because someone could have overridden
+ // "resolve" on the canonical Promise constructor.
+ RootedValue nextPromise(cx);
+ RootedValue staticResolve(cx);
+ if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve))
+ return false;
+
+ FixedInvokeArgs<1> resolveArgs(cx);
+ resolveArgs[0].set(nextValue);
+ if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise))
+ return false;
+
+ // Step j.
+ RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllResolveElementFunction,
+ 1, nullptr,
+ gc::AllocKind::FUNCTION_EXTENDED,
+ GenericObject));
+ if (!resolveFunc)
+ return false;
+
+ // Steps k,m,n.
+ resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal);
+
+ // Step l.
+ resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex,
+ Int32Value(index));
+
+ // Steps o-p.
+ dataHolder->increaseRemainingCount();
+
+ // Step q.
+ RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc));
+ if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal))
+ return false;
+
+ // Step r.
+ index++;
+ MOZ_ASSERT(index > 0);
+ }
+}
+
+// ES2016, 25.4.4.1.2.
+static bool
+PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedFunction resolve(cx, &args.callee().as<JSFunction>());
+ RootedValue xVal(cx, args.get(0));
+
+ // Step 1.
+ RootedValue dataVal(cx, resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data));
+
+ // Step 2.
+ // We use the existence of the data holder as a signal for whether the
+ // Promise was already resolved. Upon resolution, it's reset to
+ // `undefined`.
+ if (dataVal.isUndefined()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ Rooted<PromiseAllDataHolder*> data(cx, &dataVal.toObject().as<PromiseAllDataHolder>());
+
+ // Step 3.
+ resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, UndefinedValue());
+
+ // Step 4.
+ int32_t index = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex)
+ .toInt32();
+
+ // Step 5.
+ RootedValue valuesVal(cx, data->valuesArray());
+ RootedObject valuesObj(cx, &valuesVal.toObject());
+ bool valuesListIsWrapped = false;
+ if (IsWrapper(valuesObj)) {
+ valuesListIsWrapped = true;
+ // See comment for PerformPromiseAll, step 3 for why we unwrap here.
+ valuesObj = UncheckedUnwrap(valuesObj);
+ }
+ RootedNativeObject values(cx, &valuesObj->as<NativeObject>());
+
+ // Step 6 (moved under step 10).
+ // Step 7 (moved to step 9).
+
+ // Step 8.
+ // The index is guaranteed to be initialized to `undefined`.
+ if (valuesListIsWrapped) {
+ AutoCompartment ac(cx, values);
+ if (!cx->compartment()->wrap(cx, &xVal))
+ return false;
+ }
+ values->setDenseElement(index, xVal);
+
+ // Steps 7,9.
+ uint32_t remainingCount = data->decreaseRemainingCount();
+
+ // Step 10.
+ if (remainingCount == 0) {
+ // Step 10.a. (Omitted, happened in PerformPromiseAll.)
+ // Step 10.b.
+
+ // Step 6 (Adapted to work with PromiseAllDataHolder's layout).
+ RootedObject resolveAllFun(cx, data->resolveObj());
+ RootedObject promiseObj(cx, data->promiseObj());
+ if (!resolveAllFun) {
+ if (!FulfillMaybeWrappedPromise(cx, promiseObj, valuesVal))
+ return false;
+ } else {
+ if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj))
+ return false;
+ }
+ }
+
+ // Step 11.
+ args.rval().setUndefined();
+ return true;
+}
+
+static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator,
+ HandleObject C, HandleObject promiseObj,
+ HandleObject resolve, HandleObject reject);
+
+// ES2016, 25.4.4.3.
+static bool
+Promise_static_race(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedValue iterable(cx, args.get(0));
+
+ // Step 2 (reordered).
+ RootedValue CVal(cx, args.thisv());
+ if (!CVal.isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
+ "Receiver of Promise.race call");
+ return false;
+ }
+
+ // Step 1.
+ RootedObject C(cx, &CVal.toObject());
+
+ // Step 3.
+ RootedObject resultPromise(cx);
+ RootedObject resolve(cx);
+ RootedObject reject(cx);
+ if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false))
+ return false;
+
+ // Steps 4-5.
+ JS::ForOfIterator iter(cx);
+ if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable))
+ return AbruptRejectPromise(cx, args, resultPromise, reject);
+
+ if (!iter.valueIsIterable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
+ "Argument of Promise.race");
+ return AbruptRejectPromise(cx, args, resultPromise, reject);
+ }
+
+ // Step 6 (implicit).
+
+ // Step 7.
+ bool result = PerformPromiseRace(cx, iter, C, resultPromise, resolve, reject);
+
+ // Step 8.
+ if (!result) {
+ // Step 8.a.
+ // TODO: implement iterator closing.
+
+ // Step 8.b.
+ return AbruptRejectPromise(cx, args, resultPromise, reject);
+ }
+
+ // Step 9.
+ args.rval().setObject(*resultPromise);
+ return true;
+}
+
+// ES2016, 25.4.4.3.1.
+static MOZ_MUST_USE bool
+PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
+ HandleObject promiseObj, HandleObject resolve, HandleObject reject)
+{
+ MOZ_ASSERT(C->isConstructor());
+ RootedValue CVal(cx, ObjectValue(*C));
+
+ RootedValue nextValue(cx);
+ RootedValue resolveFunVal(cx, ObjectOrNullValue(resolve));
+ RootedValue rejectFunVal(cx, ObjectOrNullValue(reject));
+ bool done;
+
+ while (true) {
+ // Steps a-c, e-g.
+ if (!iterator.next(&nextValue, &done))
+ return false;
+
+ // Step d.
+ if (done) {
+ // Step d.i.
+ // TODO: implement iterator closing.
+
+ // Step d.ii.
+ return true;
+ }
+
+ // Step h.
+ // Sadly, because someone could have overridden
+ // "resolve" on the canonical Promise constructor.
+ RootedValue nextPromise(cx);
+ RootedValue staticResolve(cx);
+ if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve))
+ return false;
+
+ FixedInvokeArgs<1> resolveArgs(cx);
+ resolveArgs[0].set(nextValue);
+ if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise))
+ return false;
+
+ // Step i.
+ if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal))
+ return false;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Shouldn't reach the end of PerformPromiseRace");
+}
+
+// ES2016, Sub-steps of 25.4.4.4 and 25.4.4.5.
+static MOZ_MUST_USE bool
+CommonStaticResolveRejectImpl(JSContext* cx, unsigned argc, Value* vp, ResolutionMode mode)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedValue x(cx, args.get(0));
+
+ // Steps 1-2.
+ if (!args.thisv().isObject()) {
+ const char* msg = mode == ResolveMode
+ ? "Receiver of Promise.resolve call"
+ : "Receiver of Promise.reject call";
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, msg);
+ return false;
+ }
+ RootedValue cVal(cx, args.thisv());
+ RootedObject C(cx, &cVal.toObject());
+
+ // Step 3 of Resolve.
+ if (mode == ResolveMode && x.isObject()) {
+ RootedObject xObj(cx, &x.toObject());
+ bool isPromise = false;
+ if (xObj->is<PromiseObject>()) {
+ isPromise = true;
+ } else if (IsWrapper(xObj)) {
+ // Treat instances of Promise from other compartments as Promises
+ // here, too.
+ // It's important to do the GetProperty for the `constructor`
+ // below through the wrapper, because wrappers can change the
+ // outcome, so instead of unwrapping and then performing the
+ // GetProperty, just check here and then operate on the original
+ // object again.
+ RootedObject unwrappedObject(cx, CheckedUnwrap(xObj));
+ if (unwrappedObject && unwrappedObject->is<PromiseObject>())
+ isPromise = true;
+ }
+ if (isPromise) {
+ RootedValue ctorVal(cx);
+ if (!GetProperty(cx, xObj, xObj, cx->names().constructor, &ctorVal))
+ return false;
+ if (ctorVal == cVal) {
+ args.rval().set(x);
+ return true;
+ }
+ }
+ }
+
+ // Step 4 of Resolve, 3 of Reject.
+ RootedObject promise(cx);
+ RootedObject resolveFun(cx);
+ RootedObject rejectFun(cx);
+ if (!NewPromiseCapability(cx, C, &promise, &resolveFun, &rejectFun, true))
+ return false;
+
+ // Step 5 of Resolve, 4 of Reject.
+ if (!RunResolutionFunction(cx, mode == ResolveMode ? resolveFun : rejectFun, x, mode, promise))
+ return false;
+
+ // Step 6 of Resolve, 4 of Reject.
+ args.rval().setObject(*promise);
+ return true;
+}
+
+/**
+ * ES2016, 25.4.4.4, Promise.reject.
+ */
+bool
+js::Promise_reject(JSContext* cx, unsigned argc, Value* vp)
+{
+ return CommonStaticResolveRejectImpl(cx, argc, vp, RejectMode);
+}
+
+/**
+ * Unforgeable version of ES2016, 25.4.4.4, Promise.reject.
+ */
+/* static */ JSObject*
+PromiseObject::unforgeableReject(JSContext* cx, HandleValue value)
+{
+ // Steps 1-2 (omitted).
+
+ // Roughly step 3.
+ Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx));
+ if (!promise)
+ return nullptr;
+
+ // Roughly step 4.
+ if (!ResolvePromise(cx, promise, value, JS::PromiseState::Rejected))
+ return nullptr;
+
+ // Step 5.
+ return promise;
+}
+
+/**
+ * ES2016, 25.4.4.5, Promise.resolve.
+ */
+bool
+js::Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp)
+{
+ return CommonStaticResolveRejectImpl(cx, argc, vp, ResolveMode);
+}
+
+/**
+ * Unforgeable version of ES2016, 25.4.4.5, Promise.resolve.
+ */
+/* static */ JSObject*
+PromiseObject::unforgeableResolve(JSContext* cx, HandleValue value)
+{
+ // Steps 1-2 (omitted).
+
+ // Step 3.
+ if (value.isObject()) {
+ JSObject* obj = &value.toObject();
+ if (IsWrapper(obj))
+ obj = CheckedUnwrap(obj);
+ // Instead of getting the `constructor` property, do an unforgeable
+ // check.
+ if (obj && obj->is<PromiseObject>())
+ return obj;
+ }
+
+ // Step 4.
+ Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx));
+ if (!promise)
+ return nullptr;
+
+ // Steps 5.
+ if (!ResolvePromiseInternal(cx, promise, value))
+ return nullptr;
+
+ // Step 6.
+ return promise;
+}
+
+// ES2016, 25.4.4.6, implemented in Promise.js.
+
+// ES2016, 25.4.5.1, implemented in Promise.js.
+
+static PromiseReactionRecord*
+NewReactionRecord(JSContext* cx, HandleObject resultPromise, HandleValue onFulfilled,
+ HandleValue onRejected, HandleObject resolve, HandleObject reject,
+ HandleObject incumbentGlobalObject)
+{
+ // Either of the following conditions must be met:
+ // * resultPromise is a PromiseObject
+ // * resolve and reject are callable
+ // except for Async Generator, there resultPromise can be nullptr.
+ MOZ_ASSERT_IF(resultPromise && !resultPromise->is<PromiseObject>(), resolve);
+ MOZ_ASSERT_IF(resultPromise && !resultPromise->is<PromiseObject>(), IsCallable(resolve));
+ MOZ_ASSERT_IF(resultPromise && !resultPromise->is<PromiseObject>(), reject);
+ MOZ_ASSERT_IF(resultPromise && !resultPromise->is<PromiseObject>(), IsCallable(reject));
+
+ Rooted<PromiseReactionRecord*> reaction(cx, NewObjectWithClassProto<PromiseReactionRecord>(cx));
+ if (!reaction)
+ return nullptr;
+
+ assertSameCompartment(cx, resultPromise);
+ assertSameCompartment(cx, onFulfilled);
+ assertSameCompartment(cx, onRejected);
+ assertSameCompartment(cx, resolve);
+ assertSameCompartment(cx, reject);
+ assertSameCompartment(cx, incumbentGlobalObject);
+
+ reaction->setFixedSlot(ReactionRecordSlot_Promise, ObjectOrNullValue(resultPromise));
+ reaction->setFixedSlot(ReactionRecordSlot_Flags, Int32Value(0));
+ reaction->setFixedSlot(ReactionRecordSlot_OnFulfilled, onFulfilled);
+ reaction->setFixedSlot(ReactionRecordSlot_OnRejected, onRejected);
+ reaction->setFixedSlot(ReactionRecordSlot_Resolve, ObjectOrNullValue(resolve));
+ reaction->setFixedSlot(ReactionRecordSlot_Reject, ObjectOrNullValue(reject));
+ reaction->setFixedSlot(ReactionRecordSlot_IncumbentGlobalObject,
+ ObjectOrNullValue(incumbentGlobalObject));
+
+ return reaction;
+}
+
+// ES2016, 25.4.5.3., steps 3-5.
+MOZ_MUST_USE bool
+js::OriginalPromiseThen(JSContext* cx, Handle<PromiseObject*> promise,
+ HandleValue onFulfilled, HandleValue onRejected,
+ MutableHandleObject dependent, bool createDependent)
+{
+ RootedObject promiseObj(cx, promise);
+ if (promise->compartment() != cx->compartment()) {
+ if (!cx->compartment()->wrap(cx, &promiseObj))
+ return false;
+ }
+
+ RootedObject resultPromise(cx);
+ RootedObject resolve(cx);
+ RootedObject reject(cx);
+
+ if (createDependent) {
+ // Step 3.
+ RootedValue ctorVal(cx);
+ if (!SpeciesConstructor(cx, promiseObj, JSProto_Promise, &ctorVal))
+ return false;
+ RootedObject C(cx, &ctorVal.toObject());
+
+ // Step 4.
+ if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, true))
+ return false;
+ }
+
+ // Step 5.
+ if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise, resolve, reject))
+ return false;
+
+ dependent.set(resultPromise);
+ return true;
+}
+
+static MOZ_MUST_USE bool PerformPromiseThenWithReaction(JSContext* cx,
+ Handle<PromiseObject*> promise,
+ Handle<PromiseReactionRecord*> reaction);
+
+// Some async/await functions are implemented here instead of
+// js/src/builtin/AsyncFunction.cpp, to call Promise internal functions.
+
+// Async Functions proposal 1.1.8 and 1.2.14 step 1.
+MOZ_MUST_USE PromiseObject*
+js::CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal)
+{
+ // Step 1.
+ Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithDefaultResolution(cx));
+ if (!promise)
+ return nullptr;
+
+ AddPromiseFlags(*promise, PROMISE_FLAG_ASYNC);
+ promise->setFixedSlot(PromiseSlot_AwaitGenerator, generatorVal);
+ return promise;
+}
+
+// Async Functions proposal 2.2 steps 3.f, 3.g.
+MOZ_MUST_USE bool
+js::AsyncFunctionThrown(JSContext* cx, Handle<PromiseObject*> resultPromise)
+{
+ // Step 3.f.
+ RootedValue exc(cx);
+ if (!MaybeGetAndClearException(cx, &exc))
+ return false;
+
+ if (!RejectMaybeWrappedPromise(cx, resultPromise, exc))
+ return false;
+
+ // Step 3.g.
+ return true;
+}
+
+// Async Functions proposal 2.2 steps 3.d-e, 3.g.
+MOZ_MUST_USE bool
+js::AsyncFunctionReturned(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value)
+{
+ // Steps 3.d-e.
+ if (!ResolvePromiseInternal(cx, resultPromise, value))
+ return false;
+
+ // Step 3.g.
+ return true;
+}
+
+// Async Functions proposal 2.3 steps 2-8.
+MOZ_MUST_USE bool
+js::AsyncFunctionAwait(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value)
+{
+ // Step 2.
+ Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithDefaultResolution(cx));
+ if (!promise)
+ return false;
+
+ // Steps 3.
+ if (!ResolvePromiseInternal(cx, promise, value))
+ return false;
+
+ // Steps 4-5.
+ RootedValue onFulfilled(cx, Int32Value(PROMISE_HANDLER_AWAIT_FULFILLED));
+ RootedValue onRejected(cx, Int32Value(PROMISE_HANDLER_AWAIT_REJECTED));
+
+ RootedObject incumbentGlobal(cx);
+ if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal))
+ return false;
+
+ // Steps 6-7.
+ Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, resultPromise,
+ onFulfilled, onRejected,
+ nullptr, nullptr,
+ incumbentGlobal));
+ if (!reaction)
+ return false;
+
+ reaction->setIsAwait();
+
+ // Step 8.
+ return PerformPromiseThenWithReaction(cx, promise, reaction);
+}
+
+// ES2016, 25.4.5.3.
+bool
+js::Promise_then(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ RootedValue promiseVal(cx, args.thisv());
+
+ RootedValue onFulfilled(cx, args.get(0));
+ RootedValue onRejected(cx, args.get(1));
+
+ // Step 2.
+ if (!promiseVal.isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
+ "Receiver of Promise.prototype.then call");
+ return false;
+ }
+ RootedObject promiseObj(cx, &promiseVal.toObject());
+ Rooted<PromiseObject*> promise(cx);
+
+ bool isPromise = promiseObj->is<PromiseObject>();
+ if (isPromise) {
+ promise = &promiseObj->as<PromiseObject>();
+ } else {
+ RootedObject unwrappedPromiseObj(cx, CheckedUnwrap(promiseObj));
+ if (!unwrappedPromiseObj) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED);
+ return false;
+ }
+ if (!unwrappedPromiseObj->is<PromiseObject>()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
+ "Promise", "then", "value");
+ return false;
+ }
+ promise = &unwrappedPromiseObj->as<PromiseObject>();
+ }
+
+ // Steps 3-5.
+ RootedObject resultPromise(cx);
+ if (!OriginalPromiseThen(cx, promise, onFulfilled, onRejected, &resultPromise, true))
+ return false;
+
+ args.rval().setObject(*resultPromise);
+ return true;
+}
+
+// ES2016, 25.4.5.3.1.
+static MOZ_MUST_USE bool
+PerformPromiseThen(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled_,
+ HandleValue onRejected_, HandleObject resultPromise,
+ HandleObject resolve, HandleObject reject)
+{
+ // Step 1 (implicit).
+ // Step 2 (implicit).
+
+ // Step 3.
+ RootedValue onFulfilled(cx, onFulfilled_);
+ if (!IsCallable(onFulfilled))
+ onFulfilled = Int32Value(PROMISE_HANDLER_IDENTITY);
+
+ // Step 4.
+ RootedValue onRejected(cx, onRejected_);
+ if (!IsCallable(onRejected))
+ onRejected = Int32Value(PROMISE_HANDLER_THROWER);
+
+ RootedObject incumbentGlobal(cx);
+ if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal))
+ return false;
+
+ // Step 7.
+ Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, resultPromise,
+ onFulfilled, onRejected,
+ resolve, reject,
+ incumbentGlobal));
+ if (!reaction)
+ return false;
+
+ return PerformPromiseThenWithReaction(cx, promise, reaction);
+}
+
+static MOZ_MUST_USE bool
+PerformPromiseThenWithReaction(JSContext* cx, Handle<PromiseObject*> promise,
+ Handle<PromiseReactionRecord*> reaction)
+{
+ JS::PromiseState state = promise->state();
+ int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32();
+ if (state == JS::PromiseState::Pending) {
+ // Steps 5,6 (reordered).
+ // Instead of creating separate reaction records for fulfillment and
+ // rejection, we create a combined record. All places we use the record
+ // can handle that.
+ if (!AddPromiseReaction(cx, promise, reaction))
+ return false;
+ }
+
+ // Steps 8,9.
+ else {
+ // Step 9.a.
+ MOZ_ASSERT_IF(state != JS::PromiseState::Fulfilled, state == JS::PromiseState::Rejected);
+
+ // Step 8.a. / 9.b.
+ RootedValue valueOrReason(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult));
+
+ // We might be operating on a promise from another compartment. In
+ // that case, we need to wrap the result/reason value before using it.
+ if (!cx->compartment()->wrap(cx, &valueOrReason))
+ return false;
+
+ // Step 9.c.
+ if (state == JS::PromiseState::Rejected && !(flags & PROMISE_FLAG_HANDLED))
+ cx->runtime()->removeUnhandledRejectedPromise(cx, promise);
+
+ // Step 8.b. / 9.d.
+ if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state))
+ return false;
+ }
+
+ // Step 10.
+ promise->setFixedSlot(PromiseSlot_Flags, Int32Value(flags | PROMISE_FLAG_HANDLED));
+
+ // Step 11.
+ return true;
+}
+
+/**
+ * Calls |promise.then| with the provided hooks and adds |blockedPromise| to
+ * its list of dependent promises. Used by |Promise.all| and |Promise.race|.
+ *
+ * If |promise.then| is the original |Promise.prototype.then| function and
+ * the call to |promise.then| would use the original |Promise| constructor to
+ * create the resulting promise, this function skips the call to |promise.then|
+ * and thus creating a new promise that would not be observable by content.
+ */
+static MOZ_MUST_USE bool
+BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromise_,
+ HandleValue onFulfilled, HandleValue onRejected)
+{
+ RootedValue thenVal(cx);
+ if (!GetProperty(cx, promiseVal, cx->names().then, &thenVal))
+ return false;
+
+ RootedObject promiseObj(cx);
+ if (promiseVal.isObject())
+ promiseObj = &promiseVal.toObject();
+
+ if (promiseObj && promiseObj->is<PromiseObject>() && IsNativeFunction(thenVal, Promise_then)) {
+ // |promise| is an unwrapped Promise, and |then| is the original
+ // |Promise.prototype.then|, inline it here.
+ // 25.4.5.3., step 3.
+ RootedObject PromiseCtor(cx);
+ if (!GetBuiltinConstructor(cx, JSProto_Promise, &PromiseCtor))
+ return false;
+ RootedValue PromiseCtorVal(cx, ObjectValue(*PromiseCtor));
+ RootedValue CVal(cx);
+ if (!SpeciesConstructor(cx, promiseObj, PromiseCtorVal, &CVal))
+ return false;
+ RootedObject C(cx, &CVal.toObject());
+
+ RootedObject resultPromise(cx, blockedPromise_);
+ RootedObject resolveFun(cx);
+ RootedObject rejectFun(cx);
+
+ // By default, the blocked promise is added as an extra entry to the
+ // rejected promises list.
+ bool addToDependent = true;
+
+ if (C == PromiseCtor && resultPromise->is<PromiseObject>()) {
+ addToDependent = false;
+ } else {
+ // 25.4.5.3., step 4.
+ if (!NewPromiseCapability(cx, C, &resultPromise, &resolveFun, &rejectFun, true))
+ return false;
+ }
+
+ // 25.4.5.3., step 5.
+ Rooted<PromiseObject*> promise(cx, &promiseObj->as<PromiseObject>());
+ if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise,
+ resolveFun, rejectFun))
+ {
+ return false;
+ }
+
+ if (!addToDependent)
+ return true;
+ } else {
+ // Optimization failed, do the normal call.
+ RootedValue rval(cx);
+ if (!Call(cx, thenVal, promiseVal, onFulfilled, onRejected, &rval))
+ return false;
+ }
+
+ // In case the value to depend on isn't an object at all, there's nothing
+ // more to do here: we can only add reactions to Promise objects
+ // (potentially after unwrapping them), and non-object values can't be
+ // Promise objects. This can happen if Promise.all is called on an object
+ // with a `resolve` method that returns primitives.
+ if (!promiseObj)
+ return true;
+
+ // The object created by the |promise.then| call or the inlined version
+ // of it above is visible to content (either because |promise.then| was
+ // overridden by content and could leak it, or because a constructor
+ // other than the original value of |Promise| was used to create it).
+ // To have both that object and |blockedPromise| show up as dependent
+ // promises in the debugger, add a dummy reaction to the list of reject
+ // reactions that contains |blockedPromise|, but otherwise does nothing.
+ RootedObject unwrappedPromiseObj(cx, promiseObj);
+ RootedObject blockedPromise(cx, blockedPromise_);
+
+ mozilla::Maybe<AutoCompartment> ac;
+ if (IsProxy(promiseObj)) {
+ unwrappedPromiseObj = CheckedUnwrap(promiseObj);
+ if (!unwrappedPromiseObj) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_ACCESS_DENIED);
+ return false;
+ }
+ if (JS_IsDeadWrapper(unwrappedPromiseObj)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
+ return false;
+ }
+ ac.emplace(cx, unwrappedPromiseObj);
+ if (!cx->compartment()->wrap(cx, &blockedPromise))
+ return false;
+ }
+
+ // If either the object to depend on or the object that gets blocked isn't
+ // a, maybe-wrapped, Promise instance, we ignore it. All this does is lose
+ // some small amount of debug information in scenarios that are highly
+ // unlikely to occur in useful code.
+ if (!unwrappedPromiseObj->is<PromiseObject>())
+ return true;
+ if (!blockedPromise_->is<PromiseObject>())
+ return true;
+
+ Rooted<PromiseObject*> promise(cx, &unwrappedPromiseObj->as<PromiseObject>());
+ return AddPromiseReaction(cx, promise, UndefinedHandleValue, UndefinedHandleValue,
+ blockedPromise, nullptr, nullptr, nullptr);
+}
+
+static MOZ_MUST_USE bool
+AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise,
+ Handle<PromiseReactionRecord*> reaction)
+{
+ RootedValue reactionVal(cx, ObjectValue(*reaction));
+
+ // The code that creates Promise reactions can handle wrapped Promises,
+ // unwrapping them as needed. That means that the `promise` and `reaction`
+ // objects we have here aren't necessarily from the same compartment. In
+ // order to store the reaction on the promise, we have to ensure that it
+ // is properly wrapped.
+ mozilla::Maybe<AutoCompartment> ac;
+ if (promise->compartment() != cx->compartment()) {
+ ac.emplace(cx, promise);
+ if (!cx->compartment()->wrap(cx, &reactionVal))
+ return false;
+ }
+
+ // 25.4.5.3.1 steps 7.a,b.
+ RootedValue reactionsVal(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult));
+ RootedNativeObject reactions(cx);
+
+ if (reactionsVal.isUndefined()) {
+ // If no reactions existed so far, just store the reaction record directly.
+ promise->setFixedSlot(PromiseSlot_ReactionsOrResult, reactionVal);
+ return true;
+ }
+
+ RootedObject reactionsObj(cx, &reactionsVal.toObject());
+
+ // If only a single reaction exists, it's stored directly instead of in a
+ // list. In that case, `reactionsObj` might be a wrapper, which we can
+ // always safely unwrap.
+ if (IsProxy(reactionsObj)) {
+ reactionsObj = UncheckedUnwrap(reactionsObj);
+ if (JS_IsDeadWrapper(reactionsObj)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
+ return false;
+ }
+ MOZ_ASSERT(reactionsObj->is<PromiseReactionRecord>());
+ }
+
+ if (reactionsObj->is<PromiseReactionRecord>()) {
+ // If a single reaction existed so far, create a list and store the
+ // old and the new reaction in it.
+ reactions = NewDenseFullyAllocatedArray(cx, 2);
+ if (!reactions)
+ return false;
+ if (reactions->ensureDenseElements(cx, 0, 2) != DenseElementResult::Success)
+ return false;
+
+ reactions->setDenseElement(0, reactionsVal);
+ reactions->setDenseElement(1, reactionVal);
+
+ promise->setFixedSlot(PromiseSlot_ReactionsOrResult, ObjectValue(*reactions));
+ } else {
+ // Otherwise, just store the new reaction.
+ reactions = &reactionsObj->as<NativeObject>();
+ uint32_t len = reactions->getDenseInitializedLength();
+ if (reactions->ensureDenseElements(cx, 0, len + 1) != DenseElementResult::Success)
+ return false;
+ reactions->setDenseElement(len, reactionVal);
+ }
+
+ return true;
+}
+
+static MOZ_MUST_USE bool
+AddPromiseReaction(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled,
+ HandleValue onRejected, HandleObject dependentPromise,
+ HandleObject resolve, HandleObject reject, HandleObject incumbentGlobal)
+{
+ if (promise->state() != JS::PromiseState::Pending)
+ return true;
+
+ Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, dependentPromise,
+ onFulfilled, onRejected,
+ resolve, reject,
+ incumbentGlobal));
+ if (!reaction)
+ return false;
+ return AddPromiseReaction(cx, promise, reaction);
+}
+
+namespace {
+// Generator used by PromiseObject::getID.
+mozilla::Atomic<uint64_t> gIDGenerator(0);
+} // namespace
+
+double
+PromiseObject::lifetime()
+{
+ return MillisecondsSinceStartup() - allocationTime();
+}
+
+uint64_t
+PromiseObject::getID()
+{
+ Value idVal(getFixedSlot(PromiseSlot_Id));
+ if (idVal.isUndefined()) {
+ idVal.setDouble(++gIDGenerator);
+ setFixedSlot(PromiseSlot_Id, idVal);
+ }
+ return uint64_t(idVal.toNumber());
+}
+
+/**
+ * Returns all promises that directly depend on this one. That means those
+ * created by calling `then` on this promise, or the promise returned by
+ * `Promise.all(iterable)` or `Promise.race(iterable)`, with this promise
+ * being a member of the passed-in `iterable`.
+ *
+ * Per spec, we should have separate lists of reaction records for the
+ * fulfill and reject cases. As an optimization, we have only one of those,
+ * containing the required data for both cases. So we just walk that list
+ * and extract the dependent promises from all reaction records.
+ */
+bool
+PromiseObject::dependentPromises(JSContext* cx, MutableHandle<GCVector<Value>> values)
+{
+ if (state() != JS::PromiseState::Pending)
+ return true;
+
+ RootedValue reactionsVal(cx, getFixedSlot(PromiseSlot_ReactionsOrResult));
+
+ // If no reactions are pending, we don't have list and are done.
+ if (reactionsVal.isNullOrUndefined())
+ return true;
+
+ RootedNativeObject reactions(cx, &reactionsVal.toObject().as<NativeObject>());
+
+ // If only a single reaction is pending, it's stored directly.
+ if (reactions->is<PromiseReactionRecord>()) {
+ // Not all reactions have a Promise on them.
+ RootedObject promiseObj(cx, reactions->as<PromiseReactionRecord>().promise());
+ if (!promiseObj)
+ return true;
+
+ if (!values.growBy(1))
+ return false;
+
+ values[0].setObject(*promiseObj);
+ return true;
+ }
+
+ uint32_t len = reactions->getDenseInitializedLength();
+ MOZ_ASSERT(len >= 2);
+
+ size_t valuesIndex = 0;
+ Rooted<PromiseReactionRecord*> reaction(cx);
+ for (size_t i = 0; i < len; i++) {
+ reaction = &reactions->getDenseElement(i).toObject().as<PromiseReactionRecord>();
+
+ // Not all reactions have a Promise on them.
+ RootedObject promiseObj(cx, reaction->promise());
+ if (!promiseObj)
+ continue;
+ if (!values.growBy(1))
+ return false;
+
+ values[valuesIndex++].setObject(*promiseObj);
+ }
+
+ return true;
+}
+
+bool
+PromiseObject::resolve(JSContext* cx, HandleValue resolutionValue)
+{
+ MOZ_ASSERT(!PromiseHasAnyFlag(*this, PROMISE_FLAG_ASYNC));
+ if (state() != JS::PromiseState::Pending)
+ return true;
+
+ RootedObject resolveFun(cx, GetResolveFunctionFromPromise(this));
+ RootedValue funVal(cx, ObjectValue(*resolveFun));
+
+ // For xray'd Promises, the resolve fun may have been created in another
+ // compartment. For the call below to work in that case, wrap the
+ // function into the current compartment.
+ if (!cx->compartment()->wrap(cx, &funVal))
+ return false;
+
+ FixedInvokeArgs<1> args(cx);
+ args[0].set(resolutionValue);
+
+ RootedValue dummy(cx);
+ return Call(cx, funVal, UndefinedHandleValue, args, &dummy);
+}
+
+bool
+PromiseObject::reject(JSContext* cx, HandleValue rejectionValue)
+{
+ MOZ_ASSERT(!PromiseHasAnyFlag(*this, PROMISE_FLAG_ASYNC));
+ if (state() != JS::PromiseState::Pending)
+ return true;
+
+ RootedValue funVal(cx, this->getFixedSlot(PromiseSlot_RejectFunction));
+ MOZ_ASSERT(IsCallable(funVal));
+
+ FixedInvokeArgs<1> args(cx);
+ args[0].set(rejectionValue);
+
+ RootedValue dummy(cx);
+ return Call(cx, funVal, UndefinedHandleValue, args, &dummy);
+}
+
+void
+PromiseObject::onSettled(JSContext* cx)
+{
+ Rooted<PromiseObject*> promise(cx, this);
+ RootedObject stack(cx);
+ if (cx->options().asyncStack() || cx->compartment()->isDebuggee()) {
+ if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames()))) {
+ cx->clearPendingException();
+ return;
+ }
+ }
+ promise->setFixedSlot(PromiseSlot_ResolutionSite, ObjectOrNullValue(stack));
+ promise->setFixedSlot(PromiseSlot_ResolutionTime, DoubleValue(MillisecondsSinceStartup()));
+
+ if (promise->state() == JS::PromiseState::Rejected && promise->isUnhandled())
+ cx->runtime()->addUnhandledRejectedPromise(cx, promise);
+
+ JS::dbg::onPromiseSettled(cx, promise);
+}
+
+PromiseTask::PromiseTask(JSContext* cx, Handle<PromiseObject*> promise)
+ : runtime_(cx),
+ promise_(cx, promise)
+{}
+
+PromiseTask::~PromiseTask()
+{
+ MOZ_ASSERT(CurrentThreadCanAccessZone(promise_->zone()));
+}
+
+void
+PromiseTask::finish(JSContext* cx)
+{
+ MOZ_ASSERT(cx == runtime_);
+ {
+ // We can't leave a pending exception when returning to the caller so do
+ // the same thing as Gecko, which is to ignore the error. This should
+ // only happen due to OOM or interruption.
+ AutoCompartment ac(cx, promise_);
+ if (!finishPromise(cx, promise_))
+ cx->clearPendingException();
+ }
+ js_delete(this);
+}
+
+void
+PromiseTask::cancel(JSContext* cx)
+{
+ MOZ_ASSERT(cx == runtime_);
+ js_delete(this);
+}
+
+bool
+PromiseTask::executeAndFinish(JSContext* cx)
+{
+ MOZ_ASSERT(!CanUseExtraThreads());
+ execute();
+ return finishPromise(cx, promise_);
+}
+
+static JSObject*
+CreatePromisePrototype(JSContext* cx, JSProtoKey key)
+{
+ return cx->global()->createBlankPrototype(cx, &PromiseObject::protoClass_);
+}
+
+static const JSFunctionSpec promise_methods[] = {
+ JS_SELF_HOSTED_FN("catch", "Promise_catch", 1, 0),
+ JS_FN("then", Promise_then, 2, 0),
+ JS_FS_END
+};
+
+static const JSPropertySpec promise_properties[] = {
+ JS_STRING_SYM_PS(toStringTag, "Promise", JSPROP_READONLY),
+ JS_PS_END
+};
+
+static const JSFunctionSpec promise_static_methods[] = {
+ JS_FN("all", Promise_static_all, 1, 0),
+ JS_FN("race", Promise_static_race, 1, 0),
+ JS_FN("reject", Promise_reject, 1, 0),
+ JS_FN("resolve", Promise_static_resolve, 1, 0),
+ JS_FS_END
+};
+
+static const JSPropertySpec promise_static_properties[] = {
+ JS_SELF_HOSTED_SYM_GET(species, "Promise_static_get_species", 0),
+ JS_PS_END
+};
+
+static const ClassSpec PromiseObjectClassSpec = {
+ GenericCreateConstructor<PromiseConstructor, 1, gc::AllocKind::FUNCTION>,
+ CreatePromisePrototype,
+ promise_static_methods,
+ promise_static_properties,
+ promise_methods,
+ promise_properties
+};
+
+const Class PromiseObject::class_ = {
+ "Promise",
+ JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Promise) |
+ JSCLASS_HAS_XRAYED_CONSTRUCTOR,
+ JS_NULL_CLASS_OPS,
+ &PromiseObjectClassSpec
+};
+
+static const ClassSpec PromiseObjectProtoClassSpec = {
+ DELEGATED_CLASSSPEC(PromiseObject::class_.spec),
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ ClassSpec::IsDelegated
+};
+
+const Class PromiseObject::protoClass_ = {
+ "PromiseProto",
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Promise),
+ JS_NULL_CLASS_OPS,
+ &PromiseObjectProtoClassSpec
+};
diff --git a/js/src/builtin/Promise.h b/js/src/builtin/Promise.h
new file mode 100644
index 000000000..bb4778631
--- /dev/null
+++ b/js/src/builtin/Promise.h
@@ -0,0 +1,191 @@
+/* -*- 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/. */
+
+#ifndef builtin_Promise_h
+#define builtin_Promise_h
+
+#include "builtin/SelfHostingDefines.h"
+#include "vm/NativeObject.h"
+
+namespace js {
+
+enum PromiseSlots {
+ PromiseSlot_Flags = 0,
+ PromiseSlot_ReactionsOrResult,
+ PromiseSlot_RejectFunction,
+ PromiseSlot_AwaitGenerator = PromiseSlot_RejectFunction,
+ PromiseSlot_AllocationSite,
+ PromiseSlot_ResolutionSite,
+ PromiseSlot_AllocationTime,
+ PromiseSlot_ResolutionTime,
+ PromiseSlot_Id,
+ PromiseSlots,
+};
+
+#define PROMISE_FLAG_RESOLVED 0x1
+#define PROMISE_FLAG_FULFILLED 0x2
+#define PROMISE_FLAG_HANDLED 0x4
+#define PROMISE_FLAG_REPORTED 0x8
+#define PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION 0x10
+#define PROMISE_FLAG_DEFAULT_REJECT_FUNCTION 0x20
+#define PROMISE_FLAG_ASYNC 0x40
+
+class AutoSetNewObjectMetadata;
+
+class PromiseObject : public NativeObject
+{
+ public:
+ static const unsigned RESERVED_SLOTS = PromiseSlots;
+ static const Class class_;
+ static const Class protoClass_;
+ static PromiseObject* create(JSContext* cx, HandleObject executor,
+ HandleObject proto = nullptr, bool needsWrapping = false);
+
+ static JSObject* unforgeableResolve(JSContext* cx, HandleValue value);
+ static JSObject* unforgeableReject(JSContext* cx, HandleValue value);
+
+ JS::PromiseState state() {
+ int32_t flags = getFixedSlot(PromiseSlot_Flags).toInt32();
+ if (!(flags & PROMISE_FLAG_RESOLVED)) {
+ MOZ_ASSERT(!(flags & PROMISE_FLAG_FULFILLED));
+ return JS::PromiseState::Pending;
+ }
+ if (flags & PROMISE_FLAG_FULFILLED)
+ return JS::PromiseState::Fulfilled;
+ return JS::PromiseState::Rejected;
+ }
+ Value value() {
+ MOZ_ASSERT(state() == JS::PromiseState::Fulfilled);
+ return getFixedSlot(PromiseSlot_ReactionsOrResult);
+ }
+ Value reason() {
+ MOZ_ASSERT(state() == JS::PromiseState::Rejected);
+ return getFixedSlot(PromiseSlot_ReactionsOrResult);
+ }
+
+ MOZ_MUST_USE bool resolve(JSContext* cx, HandleValue resolutionValue);
+ MOZ_MUST_USE bool reject(JSContext* cx, HandleValue rejectionValue);
+
+ void onSettled(JSContext* cx);
+
+ double allocationTime() { return getFixedSlot(PromiseSlot_AllocationTime).toNumber(); }
+ double resolutionTime() { return getFixedSlot(PromiseSlot_ResolutionTime).toNumber(); }
+ JSObject* allocationSite() {
+ return getFixedSlot(PromiseSlot_AllocationSite).toObjectOrNull();
+ }
+ JSObject* resolutionSite() {
+ return getFixedSlot(PromiseSlot_ResolutionSite).toObjectOrNull();
+ }
+ double lifetime();
+ double timeToResolution() {
+ MOZ_ASSERT(state() != JS::PromiseState::Pending);
+ return resolutionTime() - allocationTime();
+ }
+ MOZ_MUST_USE bool dependentPromises(JSContext* cx, MutableHandle<GCVector<Value>> values);
+ uint64_t getID();
+ bool isUnhandled() {
+ MOZ_ASSERT(state() == JS::PromiseState::Rejected);
+ return !(getFixedSlot(PromiseSlot_Flags).toInt32() & PROMISE_FLAG_HANDLED);
+ }
+ void markAsReported() {
+ MOZ_ASSERT(isUnhandled());
+ int32_t flags = getFixedSlot(PromiseSlot_Flags).toInt32();
+ setFixedSlot(PromiseSlot_Flags, Int32Value(flags | PROMISE_FLAG_REPORTED));
+ }
+};
+
+/**
+ * Unforgeable version of the JS builtin Promise.all.
+ *
+ * Takes an AutoObjectVector of Promise objects and returns a promise that's
+ * resolved with an array of resolution values when all those promises have
+ * been resolved, or rejected with the rejection value of the first rejected
+ * promise.
+ *
+ * Asserts that all objects in the `promises` vector are, maybe wrapped,
+ * instances of `Promise` or a subclass of `Promise`.
+ */
+MOZ_MUST_USE JSObject*
+GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises);
+
+/**
+ * Enqueues resolve/reject reactions in the given Promise's reactions lists
+ * as though calling the original value of Promise.prototype.then.
+ *
+ * If the `createDependent` flag is not set, no dependent Promise will be
+ * created. This is used internally to implement DOM functionality.
+ * Note: In this case, the reactions pushed using this function contain a
+ * `promise` field that can contain null. That field is only ever used by
+ * devtools, which have to treat these reactions specially.
+ */
+MOZ_MUST_USE bool
+OriginalPromiseThen(JSContext* cx, Handle<PromiseObject*> promise,
+ HandleValue onFulfilled, HandleValue onRejected,
+ MutableHandleObject dependent, bool createDependent);
+
+
+MOZ_MUST_USE PromiseObject*
+CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal);
+
+MOZ_MUST_USE bool
+AsyncFunctionReturned(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value);
+
+MOZ_MUST_USE bool
+AsyncFunctionThrown(JSContext* cx, Handle<PromiseObject*> resultPromise);
+
+MOZ_MUST_USE bool
+AsyncFunctionAwait(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value);
+
+/**
+ * A PromiseTask represents a task that can be dispatched to a helper thread
+ * (via StartPromiseTask), executed (by implementing PromiseTask::execute()),
+ * and then resolved back on the original JSContext owner thread.
+ * Because it contains a PersistentRooted, a PromiseTask will only be destroyed
+ * on the JSContext's owner thread.
+ */
+class PromiseTask : public JS::AsyncTask
+{
+ JSRuntime* runtime_;
+ PersistentRooted<PromiseObject*> promise_;
+
+ // PromiseTask implements JS::AsyncTask and prevents derived classes from
+ // overriding; derived classes should implement the new pure virtual
+ // functions introduced below. Both of these methods 'delete this'.
+ void finish(JSContext* cx) override final;
+ void cancel(JSContext* cx) override final;
+
+ protected:
+ // Called by PromiseTask on the JSContext's owner thread after execute()
+ // completes on the helper thread, assuming JS::FinishAsyncTaskCallback
+ // succeeds. After this method returns, the task will be deleted.
+ virtual bool finishPromise(JSContext* cx, Handle<PromiseObject*> promise) = 0;
+
+ public:
+ PromiseTask(JSContext* cx, Handle<PromiseObject*> promise);
+ ~PromiseTask();
+ JSRuntime* runtime() const { return runtime_; }
+
+ // Called on a helper thread after StartAsyncTask. After execute()
+ // completes, the JS::FinishAsyncTaskCallback will be called. If this fails
+ // the task will be enqueued for deletion at some future point without ever
+ // calling finishPromise().
+ virtual void execute() = 0;
+
+ // May be called in the absence of helper threads to synchronously execute
+ // and finish a PromiseTask.
+ bool executeAndFinish(JSContext* cx);
+};
+
+bool
+Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp);
+bool
+Promise_reject(JSContext* cx, unsigned argc, Value* vp);
+bool
+Promise_then(JSContext* cx, unsigned argc, Value* vp);
+
+} // namespace js
+
+#endif /* builtin_Promise_h */
diff --git a/js/src/builtin/Promise.js b/js/src/builtin/Promise.js
new file mode 100644
index 000000000..704cbab0b
--- /dev/null
+++ b/js/src/builtin/Promise.js
@@ -0,0 +1,16 @@
+/* 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/. */
+
+// ES6, 25.4.4.6.
+function Promise_static_get_species() {
+ // Step 1.
+ return this;
+}
+_SetCanonicalName(Promise_static_get_species, "get [Symbol.species]");
+
+// ES6, 25.4.5.1.
+function Promise_catch(onRejected) {
+ // Steps 1-2.
+ return callContentFunction(this.then, this, undefined, onRejected);
+}
diff --git a/js/src/builtin/Reflect.cpp b/js/src/builtin/Reflect.cpp
new file mode 100644
index 000000000..2f509a226
--- /dev/null
+++ b/js/src/builtin/Reflect.cpp
@@ -0,0 +1,288 @@
+/* -*- 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 "builtin/Reflect.h"
+
+#include "jsarray.h"
+#include "jscntxt.h"
+
+#include "vm/ArgumentsObject.h"
+#include "vm/Stack.h"
+
+#include "vm/Interpreter-inl.h"
+
+using namespace js;
+
+
+/*** Reflect methods *****************************************************************************/
+
+/* ES6 26.1.3 Reflect.defineProperty(target, propertyKey, attributes) */
+static bool
+Reflect_defineProperty(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ RootedObject obj(cx, NonNullObject(cx, args.get(0)));
+ if (!obj)
+ return false;
+
+ // Steps 2-3.
+ RootedValue propertyKey(cx, args.get(1));
+ RootedId key(cx);
+ if (!ToPropertyKey(cx, propertyKey, &key))
+ return false;
+
+ // Steps 4-5.
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!ToPropertyDescriptor(cx, args.get(2), true, &desc))
+ return false;
+
+ // Step 6.
+ ObjectOpResult result;
+ if (!DefineProperty(cx, obj, key, desc, result))
+ return false;
+ args.rval().setBoolean(bool(result));
+ return true;
+}
+
+/* ES6 26.1.4 Reflect.deleteProperty (target, propertyKey) */
+static bool
+Reflect_deleteProperty(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ RootedObject target(cx, NonNullObject(cx, args.get(0)));
+ if (!target)
+ return false;
+
+ // Steps 2-3.
+ RootedValue propertyKey(cx, args.get(1));
+ RootedId key(cx);
+ if (!ToPropertyKey(cx, propertyKey, &key))
+ return false;
+
+ // Step 4.
+ ObjectOpResult result;
+ if (!DeleteProperty(cx, target, key, result))
+ return false;
+ args.rval().setBoolean(bool(result));
+ return true;
+}
+
+/* ES6 26.1.6 Reflect.get(target, propertyKey [, receiver]) */
+static bool
+Reflect_get(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ RootedObject obj(cx, NonNullObject(cx, args.get(0)));
+ if (!obj)
+ return false;
+
+ // Steps 2-3.
+ RootedValue propertyKey(cx, args.get(1));
+ RootedId key(cx);
+ if (!ToPropertyKey(cx, propertyKey, &key))
+ return false;
+
+ // Step 4.
+ RootedValue receiver(cx, args.length() > 2 ? args[2] : args.get(0));
+
+ // Step 5.
+ return GetProperty(cx, obj, receiver, key, args.rval());
+}
+
+/* ES6 26.1.7 Reflect.getOwnPropertyDescriptor(target, propertyKey) */
+static bool
+Reflect_getOwnPropertyDescriptor(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!NonNullObject(cx, args.get(0)))
+ return false;
+
+ // The other steps are identical to ES6 draft rev 32 (2015 Feb 2) 19.1.2.6
+ // Object.getOwnPropertyDescriptor.
+ return js::obj_getOwnPropertyDescriptor(cx, argc, vp);
+}
+
+/* ES6 26.1.8 Reflect.getPrototypeOf(target) */
+bool
+js::Reflect_getPrototypeOf(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ RootedObject target(cx, NonNullObject(cx, args.get(0)));
+ if (!target)
+ return false;
+
+ // Step 2.
+ RootedObject proto(cx);
+ if (!GetPrototype(cx, target, &proto))
+ return false;
+ args.rval().setObjectOrNull(proto);
+ return true;
+}
+
+/* ES6 draft 26.1.10 Reflect.isExtensible(target) */
+bool
+js::Reflect_isExtensible(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ RootedObject target(cx, NonNullObject(cx, args.get(0)));
+ if (!target)
+ return false;
+
+ // Step 2.
+ bool extensible;
+ if (!IsExtensible(cx, target, &extensible))
+ return false;
+ args.rval().setBoolean(extensible);
+ return true;
+}
+
+/* ES6 26.1.11 Reflect.ownKeys(target) */
+static bool
+Reflect_ownKeys(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (!NonNullObject(cx, args.get(0)))
+ return false;
+
+ // Steps 2-4.
+ return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS);
+}
+
+/* ES6 26.1.12 Reflect.preventExtensions(target) */
+static bool
+Reflect_preventExtensions(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ RootedObject target(cx, NonNullObject(cx, args.get(0)));
+ if (!target)
+ return false;
+
+ // Step 2.
+ ObjectOpResult result;
+ if (!PreventExtensions(cx, target, result))
+ return false;
+ args.rval().setBoolean(bool(result));
+ return true;
+}
+
+/* ES6 26.1.13 Reflect.set(target, propertyKey, V [, receiver]) */
+static bool
+Reflect_set(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ RootedObject target(cx, NonNullObject(cx, args.get(0)));
+ if (!target)
+ return false;
+
+ // Steps 2-3.
+ RootedValue propertyKey(cx, args.get(1));
+ RootedId key(cx);
+ if (!ToPropertyKey(cx, propertyKey, &key))
+ return false;
+
+ // Step 4.
+ RootedValue receiver(cx, args.length() > 3 ? args[3] : args.get(0));
+
+ // Step 5.
+ ObjectOpResult result;
+ RootedValue value(cx, args.get(2));
+ if (!SetProperty(cx, target, key, value, receiver, result))
+ return false;
+ args.rval().setBoolean(bool(result));
+ return true;
+}
+
+/*
+ * ES6 26.1.3 Reflect.setPrototypeOf(target, proto)
+ *
+ * The specification is not quite similar enough to Object.setPrototypeOf to
+ * share code.
+ */
+static bool
+Reflect_setPrototypeOf(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ RootedObject obj(cx, NonNullObject(cx, args.get(0)));
+ if (!obj)
+ return false;
+
+ // Step 2.
+ if (!args.get(1).isObjectOrNull()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
+ "Reflect.setPrototypeOf", "an object or null",
+ InformalValueTypeName(args.get(1)));
+ return false;
+ }
+ RootedObject proto(cx, args.get(1).toObjectOrNull());
+
+ // Step 4.
+ ObjectOpResult result;
+ if (!SetPrototype(cx, obj, proto, result))
+ return false;
+ args.rval().setBoolean(bool(result));
+ return true;
+}
+
+static const JSFunctionSpec methods[] = {
+ JS_SELF_HOSTED_FN("apply", "Reflect_apply", 3, 0),
+ JS_SELF_HOSTED_FN("construct", "Reflect_construct", 2, 0),
+ JS_FN("defineProperty", Reflect_defineProperty, 3, 0),
+ JS_FN("deleteProperty", Reflect_deleteProperty, 2, 0),
+ JS_FN("get", Reflect_get, 2, 0),
+ JS_FN("getOwnPropertyDescriptor", Reflect_getOwnPropertyDescriptor, 2, 0),
+ JS_FN("getPrototypeOf", Reflect_getPrototypeOf, 1, 0),
+ JS_SELF_HOSTED_FN("has", "Reflect_has", 2, 0),
+ JS_FN("isExtensible", Reflect_isExtensible, 1, 0),
+ JS_FN("ownKeys", Reflect_ownKeys, 1, 0),
+ JS_FN("preventExtensions", Reflect_preventExtensions, 1, 0),
+ JS_FN("set", Reflect_set, 3, 0),
+ JS_FN("setPrototypeOf", Reflect_setPrototypeOf, 2, 0),
+ JS_FS_END
+};
+
+
+/*** Setup **************************************************************************************/
+
+JSObject*
+js::InitReflect(JSContext* cx, HandleObject obj)
+{
+ RootedObject proto(cx, obj->as<GlobalObject>().getOrCreateObjectPrototype(cx));
+ if (!proto)
+ return nullptr;
+
+ RootedObject reflect(cx, NewObjectWithGivenProto<PlainObject>(cx, proto, SingletonObject));
+ if (!reflect)
+ return nullptr;
+ if (!JS_DefineFunctions(cx, reflect, methods))
+ return nullptr;
+
+ RootedValue value(cx, ObjectValue(*reflect));
+ if (!DefineProperty(cx, obj, cx->names().Reflect, value, nullptr, nullptr, JSPROP_RESOLVING))
+ return nullptr;
+
+ obj->as<GlobalObject>().setConstructor(JSProto_Reflect, value);
+
+ return reflect;
+}
diff --git a/js/src/builtin/Reflect.h b/js/src/builtin/Reflect.h
new file mode 100644
index 000000000..24fdaa254
--- /dev/null
+++ b/js/src/builtin/Reflect.h
@@ -0,0 +1,29 @@
+/* -*- 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/. */
+
+#ifndef builtin_Reflect_h
+#define builtin_Reflect_h
+
+#include "jsobj.h"
+
+namespace js {
+
+extern JSObject*
+InitReflect(JSContext* cx, js::HandleObject obj);
+
+}
+
+namespace js {
+
+extern MOZ_MUST_USE bool
+Reflect_getPrototypeOf(JSContext* cx, unsigned argc, Value* vp);
+
+extern MOZ_MUST_USE bool
+Reflect_isExtensible(JSContext* cx, unsigned argc, Value* vp);
+
+}
+
+#endif /* builtin_Reflect_h */
diff --git a/js/src/builtin/Reflect.js b/js/src/builtin/Reflect.js
new file mode 100644
index 000000000..e3a343452
--- /dev/null
+++ b/js/src/builtin/Reflect.js
@@ -0,0 +1,112 @@
+/* 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/. */
+
+// ES2017 draft rev a785b0832b071f505a694e1946182adeab84c972
+// 7.3.17 CreateListFromArrayLike (obj [ , elementTypes ] )
+function CreateListFromArrayLikeForArgs(obj) {
+ // Step 1 (not applicable).
+
+ // Step 2.
+ assert(IsObject(obj), "object must be passed to CreateListFromArrayLikeForArgs");
+
+ // Step 3.
+ var len = ToLength(obj.length);
+
+ // This version of CreateListFromArrayLike is only used for argument lists.
+ if (len > MAX_ARGS_LENGTH)
+ ThrowRangeError(JSMSG_TOO_MANY_ARGUMENTS);
+
+ // Steps 4-6.
+ var list = std_Array(len);
+ for (var i = 0; i < len; i++)
+ _DefineDataProperty(list, i, obj[i]);
+
+ // Step 7.
+ return list;
+}
+
+// ES2017 draft rev a785b0832b071f505a694e1946182adeab84c972
+// 26.1.1 Reflect.apply ( target, thisArgument, argumentsList )
+function Reflect_apply(target, thisArgument, argumentsList) {
+ // Step 1.
+ if (!IsCallable(target))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, target));
+
+ // Step 2.
+ if (!IsObject(argumentsList))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, DecompileArg(2, argumentsList));
+
+ // Steps 2-4.
+ return callFunction(std_Function_apply, target, thisArgument, argumentsList);
+}
+
+// ES2017 draft rev a785b0832b071f505a694e1946182adeab84c972
+// 26.1.2 Reflect.construct ( target, argumentsList [ , newTarget ] )
+function Reflect_construct(target, argumentsList/*, newTarget*/) {
+ // Step 1.
+ if (!IsConstructor(target))
+ ThrowTypeError(JSMSG_NOT_CONSTRUCTOR, DecompileArg(0, target));
+
+ // Steps 2-3.
+ var newTarget;
+ if (arguments.length > 2) {
+ newTarget = arguments[2];
+ if (!IsConstructor(newTarget))
+ ThrowTypeError(JSMSG_NOT_CONSTRUCTOR, DecompileArg(2, newTarget));
+ } else {
+ newTarget = target;
+ }
+
+ // Step 4.
+ if (!IsObject(argumentsList))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, DecompileArg(1, argumentsList));
+
+ // Fast path when we can avoid calling CreateListFromArrayLikeForArgs().
+ var args = (IsPackedArray(argumentsList) && argumentsList.length <= MAX_ARGS_LENGTH)
+ ? argumentsList
+ : CreateListFromArrayLikeForArgs(argumentsList);
+
+ // Step 5.
+ switch (args.length) {
+ case 0:
+ return constructContentFunction(target, newTarget);
+ case 1:
+ return constructContentFunction(target, newTarget, SPREAD(args, 1));
+ case 2:
+ return constructContentFunction(target, newTarget, SPREAD(args, 2));
+ case 3:
+ return constructContentFunction(target, newTarget, SPREAD(args, 3));
+ case 4:
+ return constructContentFunction(target, newTarget, SPREAD(args, 4));
+ case 5:
+ return constructContentFunction(target, newTarget, SPREAD(args, 5));
+ case 6:
+ return constructContentFunction(target, newTarget, SPREAD(args, 6));
+ case 7:
+ return constructContentFunction(target, newTarget, SPREAD(args, 7));
+ case 8:
+ return constructContentFunction(target, newTarget, SPREAD(args, 8));
+ case 9:
+ return constructContentFunction(target, newTarget, SPREAD(args, 9));
+ case 10:
+ return constructContentFunction(target, newTarget, SPREAD(args, 10));
+ case 11:
+ return constructContentFunction(target, newTarget, SPREAD(args, 11));
+ case 12:
+ return constructContentFunction(target, newTarget, SPREAD(args, 12));
+ default:
+ return _ConstructFunction(target, newTarget, args);
+ }
+}
+
+// ES2017 draft rev a785b0832b071f505a694e1946182adeab84c972
+// 26.1.8 Reflect.has ( target, propertyKey )
+function Reflect_has(target, propertyKey) {
+ // Step 1.
+ if (!IsObject(target))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, DecompileArg(0, target));
+
+ // Steps 2-3 are identical to the runtime semantics of the "in" operator.
+ return propertyKey in target;
+}
diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp
new file mode 100644
index 000000000..748ff7351
--- /dev/null
+++ b/js/src/builtin/ReflectParse.cpp
@@ -0,0 +1,3752 @@
+/* -*- 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/. */
+
+/* JS reflection package. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Move.h"
+
+#include <stdlib.h>
+
+#include "jsarray.h"
+#include "jsatom.h"
+#include "jsobj.h"
+#include "jspubtd.h"
+
+#include "builtin/Reflect.h"
+#include "frontend/Parser.h"
+#include "frontend/TokenStream.h"
+#include "js/CharacterEncoding.h"
+#include "vm/RegExpObject.h"
+
+#include "jsobjinlines.h"
+
+#include "frontend/ParseNode-inl.h"
+
+using namespace js;
+using namespace js::frontend;
+
+using JS::AutoValueArray;
+using mozilla::ArrayLength;
+using mozilla::DebugOnly;
+using mozilla::Forward;
+
+enum class ParseTarget
+{
+ Script,
+ Module
+};
+
+enum ASTType {
+ AST_ERROR = -1,
+#define ASTDEF(ast, str, method) ast,
+#include "jsast.tbl"
+#undef ASTDEF
+ AST_LIMIT
+};
+
+enum AssignmentOperator {
+ AOP_ERR = -1,
+
+ /* assign */
+ AOP_ASSIGN = 0,
+ /* operator-assign */
+ AOP_PLUS, AOP_MINUS, AOP_STAR, AOP_DIV, AOP_MOD, AOP_POW,
+ /* shift-assign */
+ AOP_LSH, AOP_RSH, AOP_URSH,
+ /* binary */
+ AOP_BITOR, AOP_BITXOR, AOP_BITAND,
+
+ AOP_LIMIT
+};
+
+enum BinaryOperator {
+ BINOP_ERR = -1,
+
+ /* eq */
+ BINOP_EQ = 0, BINOP_NE, BINOP_STRICTEQ, BINOP_STRICTNE,
+ /* rel */
+ BINOP_LT, BINOP_LE, BINOP_GT, BINOP_GE,
+ /* shift */
+ BINOP_LSH, BINOP_RSH, BINOP_URSH,
+ /* arithmetic */
+ BINOP_ADD, BINOP_SUB, BINOP_STAR, BINOP_DIV, BINOP_MOD, BINOP_POW,
+ /* binary */
+ BINOP_BITOR, BINOP_BITXOR, BINOP_BITAND,
+ /* misc */
+ BINOP_IN, BINOP_INSTANCEOF,
+
+ BINOP_LIMIT
+};
+
+enum UnaryOperator {
+ UNOP_ERR = -1,
+
+ UNOP_DELETE = 0,
+ UNOP_NEG,
+ UNOP_POS,
+ UNOP_NOT,
+ UNOP_BITNOT,
+ UNOP_TYPEOF,
+ UNOP_VOID,
+ UNOP_AWAIT,
+
+ UNOP_LIMIT
+};
+
+enum VarDeclKind {
+ VARDECL_ERR = -1,
+ VARDECL_VAR = 0,
+ VARDECL_CONST,
+ VARDECL_LET,
+ VARDECL_LIMIT
+};
+
+enum PropKind {
+ PROP_ERR = -1,
+ PROP_INIT = 0,
+ PROP_GETTER,
+ PROP_SETTER,
+ PROP_MUTATEPROTO,
+ PROP_LIMIT
+};
+
+static const char* const aopNames[] = {
+ "=", /* AOP_ASSIGN */
+ "+=", /* AOP_PLUS */
+ "-=", /* AOP_MINUS */
+ "*=", /* AOP_STAR */
+ "/=", /* AOP_DIV */
+ "%=", /* AOP_MOD */
+ "**=", /* AOP_POW */
+ "<<=", /* AOP_LSH */
+ ">>=", /* AOP_RSH */
+ ">>>=", /* AOP_URSH */
+ "|=", /* AOP_BITOR */
+ "^=", /* AOP_BITXOR */
+ "&=" /* AOP_BITAND */
+};
+
+static const char* const binopNames[] = {
+ "==", /* BINOP_EQ */
+ "!=", /* BINOP_NE */
+ "===", /* BINOP_STRICTEQ */
+ "!==", /* BINOP_STRICTNE */
+ "<", /* BINOP_LT */
+ "<=", /* BINOP_LE */
+ ">", /* BINOP_GT */
+ ">=", /* BINOP_GE */
+ "<<", /* BINOP_LSH */
+ ">>", /* BINOP_RSH */
+ ">>>", /* BINOP_URSH */
+ "+", /* BINOP_PLUS */
+ "-", /* BINOP_MINUS */
+ "*", /* BINOP_STAR */
+ "/", /* BINOP_DIV */
+ "%", /* BINOP_MOD */
+ "**", /* BINOP_POW */
+ "|", /* BINOP_BITOR */
+ "^", /* BINOP_BITXOR */
+ "&", /* BINOP_BITAND */
+ "in", /* BINOP_IN */
+ "instanceof", /* BINOP_INSTANCEOF */
+};
+
+static const char* const unopNames[] = {
+ "delete", /* UNOP_DELETE */
+ "-", /* UNOP_NEG */
+ "+", /* UNOP_POS */
+ "!", /* UNOP_NOT */
+ "~", /* UNOP_BITNOT */
+ "typeof", /* UNOP_TYPEOF */
+ "void", /* UNOP_VOID */
+ "await" /* UNOP_AWAIT */
+};
+
+static const char* const nodeTypeNames[] = {
+#define ASTDEF(ast, str, method) str,
+#include "jsast.tbl"
+#undef ASTDEF
+ nullptr
+};
+
+static const char* const callbackNames[] = {
+#define ASTDEF(ast, str, method) method,
+#include "jsast.tbl"
+#undef ASTDEF
+ nullptr
+};
+
+enum YieldKind { Delegating, NotDelegating };
+
+typedef AutoValueVector NodeVector;
+
+/*
+ * ParseNode is a somewhat intricate data structure, and its invariants have
+ * evolved, making it more likely that there could be a disconnect between the
+ * parser and the AST serializer. We use these macros to check invariants on a
+ * parse node and raise a dynamic error on failure.
+ */
+#define LOCAL_ASSERT(expr) \
+ JS_BEGIN_MACRO \
+ MOZ_ASSERT(expr); \
+ if (!(expr)) { \
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_PARSE_NODE);\
+ return false; \
+ } \
+ JS_END_MACRO
+
+#define LOCAL_NOT_REACHED(expr) \
+ JS_BEGIN_MACRO \
+ MOZ_ASSERT(false); \
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_PARSE_NODE); \
+ return false; \
+ JS_END_MACRO
+
+namespace {
+
+/* Set 'result' to obj[id] if any such property exists, else defaultValue. */
+static bool
+GetPropertyDefault(JSContext* cx, HandleObject obj, HandleId id, HandleValue defaultValue,
+ MutableHandleValue result)
+{
+ bool found;
+ if (!HasProperty(cx, obj, id, &found))
+ return false;
+ if (!found) {
+ result.set(defaultValue);
+ return true;
+ }
+ return GetProperty(cx, obj, obj, id, result);
+}
+
+enum class GeneratorStyle
+{
+ None,
+ Legacy,
+ ES6
+};
+
+/*
+ * Builder class that constructs JavaScript AST node objects. See:
+ *
+ * https://developer.mozilla.org/en/SpiderMonkey/Parser_API
+ *
+ * Bug 569487: generalize builder interface
+ */
+class NodeBuilder
+{
+ typedef AutoValueArray<AST_LIMIT> CallbackArray;
+
+ JSContext* cx;
+ TokenStream* tokenStream;
+ bool saveLoc; /* save source location information? */
+ char const* src; /* source filename or null */
+ RootedValue srcval; /* source filename JS value or null */
+ CallbackArray callbacks; /* user-specified callbacks */
+ RootedValue userv; /* user-specified builder object or null */
+
+ public:
+ NodeBuilder(JSContext* c, bool l, char const* s)
+ : cx(c), tokenStream(nullptr), saveLoc(l), src(s), srcval(c), callbacks(cx),
+ userv(c)
+ {}
+
+ MOZ_MUST_USE bool init(HandleObject userobj = nullptr) {
+ if (src) {
+ if (!atomValue(src, &srcval))
+ return false;
+ } else {
+ srcval.setNull();
+ }
+
+ if (!userobj) {
+ userv.setNull();
+ for (unsigned i = 0; i < AST_LIMIT; i++) {
+ callbacks[i].setNull();
+ }
+ return true;
+ }
+
+ userv.setObject(*userobj);
+
+ RootedValue nullVal(cx, NullValue());
+ RootedValue funv(cx);
+ for (unsigned i = 0; i < AST_LIMIT; i++) {
+ const char* name = callbackNames[i];
+ RootedAtom atom(cx, Atomize(cx, name, strlen(name)));
+ if (!atom)
+ return false;
+ RootedId id(cx, AtomToId(atom));
+ if (!GetPropertyDefault(cx, userobj, id, nullVal, &funv))
+ return false;
+
+ if (funv.isNullOrUndefined()) {
+ callbacks[i].setNull();
+ continue;
+ }
+
+ if (!funv.isObject() || !funv.toObject().is<JSFunction>()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_NOT_FUNCTION,
+ JSDVG_SEARCH_STACK, funv, nullptr, nullptr, nullptr);
+ return false;
+ }
+
+ callbacks[i].set(funv);
+ }
+
+ return true;
+ }
+
+ void setTokenStream(TokenStream* ts) {
+ tokenStream = ts;
+ }
+
+ private:
+ MOZ_MUST_USE bool callbackHelper(HandleValue fun, const InvokeArgs& args, size_t i,
+ TokenPos* pos, MutableHandleValue dst)
+ {
+ // The end of the implementation of callback(). All arguments except
+ // loc have already been stored in range [0, i).
+ if (saveLoc) {
+ if (!newNodeLoc(pos, args[i]))
+ return false;
+ }
+
+ return js::Call(cx, fun, userv, args, dst);
+ }
+
+ // Helper function for callback(). Note that all Arguments must be types
+ // that convert to HandleValue, so this isn't as template-y as it seems,
+ // just variadic.
+ template <typename... Arguments>
+ MOZ_MUST_USE bool callbackHelper(HandleValue fun, const InvokeArgs& args, size_t i,
+ HandleValue head, Arguments&&... tail)
+ {
+ // Recursive loop to store the arguments into args. This eventually
+ // bottoms out in a call to the non-template callbackHelper() above.
+ args[i].set(head);
+ return callbackHelper(fun, args, i + 1, Forward<Arguments>(tail)...);
+ }
+
+ // Invoke a user-defined callback. The actual signature is:
+ //
+ // bool callback(HandleValue fun, HandleValue... args, TokenPos* pos,
+ // MutableHandleValue dst);
+ template <typename... Arguments>
+ MOZ_MUST_USE bool callback(HandleValue fun, Arguments&&... args) {
+ InvokeArgs iargs(cx);
+ if (!iargs.init(cx, sizeof...(args) - 2 + size_t(saveLoc)))
+ return false;
+
+ return callbackHelper(fun, iargs, 0, Forward<Arguments>(args)...);
+ }
+
+ // WARNING: Returning a Handle is non-standard, but it works in this case
+ // because both |v| and |UndefinedHandleValue| are definitely rooted on a
+ // previous stack frame (i.e. we're just choosing between two
+ // already-rooted values).
+ HandleValue opt(HandleValue v) {
+ MOZ_ASSERT_IF(v.isMagic(), v.whyMagic() == JS_SERIALIZE_NO_NODE);
+ return v.isMagic(JS_SERIALIZE_NO_NODE) ? JS::UndefinedHandleValue : v;
+ }
+
+ MOZ_MUST_USE bool atomValue(const char* s, MutableHandleValue dst) {
+ /*
+ * Bug 575416: instead of Atomize, lookup constant atoms in tbl file
+ */
+ RootedAtom atom(cx, Atomize(cx, s, strlen(s)));
+ if (!atom)
+ return false;
+
+ dst.setString(atom);
+ return true;
+ }
+
+ MOZ_MUST_USE bool newObject(MutableHandleObject dst) {
+ RootedPlainObject nobj(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!nobj)
+ return false;
+
+ dst.set(nobj);
+ return true;
+ }
+
+ MOZ_MUST_USE bool newArray(NodeVector& elts, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool createNode(ASTType type, TokenPos* pos, MutableHandleObject dst);
+
+ MOZ_MUST_USE bool newNodeHelper(HandleObject obj, MutableHandleValue dst) {
+ // The end of the implementation of newNode().
+ MOZ_ASSERT(obj);
+ dst.setObject(*obj);
+ return true;
+ }
+
+ template <typename... Arguments>
+ MOZ_MUST_USE bool newNodeHelper(HandleObject obj, const char *name, HandleValue value,
+ Arguments&&... rest)
+ {
+ // Recursive loop to define properties. Note that the newNodeHelper()
+ // call below passes two fewer arguments than we received, as we omit
+ // `name` and `value`. This eventually bottoms out in a call to the
+ // non-template newNodeHelper() above.
+ return defineProperty(obj, name, value)
+ && newNodeHelper(obj, Forward<Arguments>(rest)...);
+ }
+
+ // Create a node object with "type" and "loc" properties, as well as zero
+ // or more properties passed in as arguments. The signature is really more
+ // like:
+ //
+ // bool newNode(ASTType type, TokenPos* pos,
+ // {const char *name0, HandleValue value0,}...
+ // MutableHandleValue dst);
+ template <typename... Arguments>
+ MOZ_MUST_USE bool newNode(ASTType type, TokenPos* pos, Arguments&&... args) {
+ RootedObject node(cx);
+ return createNode(type, pos, &node) &&
+ newNodeHelper(node, Forward<Arguments>(args)...);
+ }
+
+ MOZ_MUST_USE bool listNode(ASTType type, const char* propName, NodeVector& elts, TokenPos* pos,
+ MutableHandleValue dst) {
+ RootedValue array(cx);
+ if (!newArray(elts, &array))
+ return false;
+
+ RootedValue cb(cx, callbacks[type]);
+ if (!cb.isNull())
+ return callback(cb, array, pos, dst);
+
+ return newNode(type, pos, propName, array, dst);
+ }
+
+ MOZ_MUST_USE bool defineProperty(HandleObject obj, const char* name, HandleValue val) {
+ MOZ_ASSERT_IF(val.isMagic(), val.whyMagic() == JS_SERIALIZE_NO_NODE);
+
+ /*
+ * Bug 575416: instead of Atomize, lookup constant atoms in tbl file
+ */
+ RootedAtom atom(cx, Atomize(cx, name, strlen(name)));
+ if (!atom)
+ return false;
+
+ /* Represent "no node" as null and ensure users are not exposed to magic values. */
+ RootedValue optVal(cx, val.isMagic(JS_SERIALIZE_NO_NODE) ? NullValue() : val);
+ return DefineProperty(cx, obj, atom->asPropertyName(), optVal);
+ }
+
+ MOZ_MUST_USE bool newNodeLoc(TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool setNodeLoc(HandleObject node, TokenPos* pos);
+
+ public:
+ /*
+ * All of the public builder methods take as their last two
+ * arguments a nullable token position and a non-nullable, rooted
+ * outparam.
+ *
+ * Any Value arguments representing optional subnodes may be a
+ * JS_SERIALIZE_NO_NODE magic value.
+ */
+
+ /*
+ * misc nodes
+ */
+
+ MOZ_MUST_USE bool program(NodeVector& elts, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool literal(HandleValue val, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool identifier(HandleValue name, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool function(ASTType type, TokenPos* pos,
+ HandleValue id, NodeVector& args, NodeVector& defaults,
+ HandleValue body, HandleValue rest, GeneratorStyle generatorStyle,
+ bool isAsync, bool isExpression, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool variableDeclarator(HandleValue id, HandleValue init, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool switchCase(HandleValue expr, NodeVector& elts, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool catchClause(HandleValue var, HandleValue guard, HandleValue body, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool prototypeMutation(HandleValue val, TokenPos* pos, MutableHandleValue dst);
+ MOZ_MUST_USE bool propertyInitializer(HandleValue key, HandleValue val, PropKind kind,
+ bool isShorthand, bool isMethod, TokenPos* pos,
+ MutableHandleValue dst);
+
+
+ /*
+ * statements
+ */
+
+ MOZ_MUST_USE bool blockStatement(NodeVector& elts, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool expressionStatement(HandleValue expr, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool emptyStatement(TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool ifStatement(HandleValue test, HandleValue cons, HandleValue alt, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool breakStatement(HandleValue label, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool continueStatement(HandleValue label, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool labeledStatement(HandleValue label, HandleValue stmt, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool throwStatement(HandleValue arg, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool returnStatement(HandleValue arg, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool forStatement(HandleValue init, HandleValue test, HandleValue update, HandleValue stmt,
+ TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool forInStatement(HandleValue var, HandleValue expr, HandleValue stmt,
+ bool isForEach, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool forOfStatement(HandleValue var, HandleValue expr, HandleValue stmt, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool withStatement(HandleValue expr, HandleValue stmt, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool whileStatement(HandleValue test, HandleValue stmt, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool doWhileStatement(HandleValue stmt, HandleValue test, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool switchStatement(HandleValue disc, NodeVector& elts, bool lexical, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool tryStatement(HandleValue body, NodeVector& guarded, HandleValue unguarded,
+ HandleValue finally, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool debuggerStatement(TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool importDeclaration(NodeVector& elts, HandleValue moduleSpec, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool importSpecifier(HandleValue importName, HandleValue bindingName, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool exportDeclaration(HandleValue decl, NodeVector& elts, HandleValue moduleSpec,
+ HandleValue isDefault, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool exportSpecifier(HandleValue bindingName, HandleValue exportName, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool exportBatchSpecifier(TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool classDefinition(bool expr, HandleValue name, HandleValue heritage,
+ HandleValue block, TokenPos* pos, MutableHandleValue dst);
+ MOZ_MUST_USE bool classMethods(NodeVector& methods, MutableHandleValue dst);
+ MOZ_MUST_USE bool classMethod(HandleValue name, HandleValue body, PropKind kind, bool isStatic,
+ TokenPos* pos, MutableHandleValue dst);
+
+ /*
+ * expressions
+ */
+
+ MOZ_MUST_USE bool binaryExpression(BinaryOperator op, HandleValue left, HandleValue right,
+ TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool unaryExpression(UnaryOperator op, HandleValue expr, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool assignmentExpression(AssignmentOperator op, HandleValue lhs, HandleValue rhs,
+ TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool updateExpression(HandleValue expr, bool incr, bool prefix, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool logicalExpression(bool lor, HandleValue left, HandleValue right, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool conditionalExpression(HandleValue test, HandleValue cons, HandleValue alt,
+ TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool sequenceExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool newExpression(HandleValue callee, NodeVector& args, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool callExpression(HandleValue callee, NodeVector& args, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool memberExpression(bool computed, HandleValue expr, HandleValue member,
+ TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool arrayExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool templateLiteral(NodeVector& elts, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool taggedTemplate(HandleValue callee, NodeVector& args, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool callSiteObj(NodeVector& raw, NodeVector& cooked, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool spreadExpression(HandleValue expr, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool computedName(HandleValue name, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool objectExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool thisExpression(TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool yieldExpression(HandleValue arg, YieldKind kind, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool comprehensionBlock(HandleValue patt, HandleValue src, bool isForEach,
+ bool isForOf, TokenPos* pos, MutableHandleValue dst);
+ MOZ_MUST_USE bool comprehensionIf(HandleValue test, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool comprehensionExpression(HandleValue body, NodeVector& blocks,
+ HandleValue filter, bool isLegacy, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool generatorExpression(HandleValue body, NodeVector& blocks, HandleValue filter,
+ bool isLegacy, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool metaProperty(HandleValue meta, HandleValue property, TokenPos* pos,
+ MutableHandleValue dst);
+
+ MOZ_MUST_USE bool super(TokenPos* pos, MutableHandleValue dst);
+
+ /*
+ * declarations
+ */
+
+ MOZ_MUST_USE bool variableDeclaration(NodeVector& elts, VarDeclKind kind, TokenPos* pos,
+ MutableHandleValue dst);
+
+ /*
+ * patterns
+ */
+
+ MOZ_MUST_USE bool arrayPattern(NodeVector& elts, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool objectPattern(NodeVector& elts, TokenPos* pos, MutableHandleValue dst);
+
+ MOZ_MUST_USE bool propertyPattern(HandleValue key, HandleValue patt, bool isShorthand,
+ TokenPos* pos, MutableHandleValue dst);
+};
+
+} /* anonymous namespace */
+
+bool
+NodeBuilder::createNode(ASTType type, TokenPos* pos, MutableHandleObject dst)
+{
+ MOZ_ASSERT(type > AST_ERROR && type < AST_LIMIT);
+
+ RootedValue tv(cx);
+ RootedPlainObject node(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!node ||
+ !setNodeLoc(node, pos) ||
+ !atomValue(nodeTypeNames[type], &tv) ||
+ !defineProperty(node, "type", tv)) {
+ return false;
+ }
+
+ dst.set(node);
+ return true;
+}
+
+bool
+NodeBuilder::newArray(NodeVector& elts, MutableHandleValue dst)
+{
+ const size_t len = elts.length();
+ if (len > UINT32_MAX) {
+ ReportAllocationOverflow(cx);
+ return false;
+ }
+ RootedObject array(cx, NewDenseFullyAllocatedArray(cx, uint32_t(len)));
+ if (!array)
+ return false;
+
+ for (size_t i = 0; i < len; i++) {
+ RootedValue val(cx, elts[i]);
+
+ MOZ_ASSERT_IF(val.isMagic(), val.whyMagic() == JS_SERIALIZE_NO_NODE);
+
+ /* Represent "no node" as an array hole by not adding the value. */
+ if (val.isMagic(JS_SERIALIZE_NO_NODE))
+ continue;
+
+ if (!DefineElement(cx, array, i, val))
+ return false;
+ }
+
+ dst.setObject(*array);
+ return true;
+}
+
+bool
+NodeBuilder::newNodeLoc(TokenPos* pos, MutableHandleValue dst)
+{
+ if (!pos) {
+ dst.setNull();
+ return true;
+ }
+
+ RootedObject loc(cx);
+ RootedObject to(cx);
+ RootedValue val(cx);
+
+ if (!newObject(&loc))
+ return false;
+
+ dst.setObject(*loc);
+
+ uint32_t startLineNum, startColumnIndex;
+ uint32_t endLineNum, endColumnIndex;
+ tokenStream->srcCoords.lineNumAndColumnIndex(pos->begin, &startLineNum, &startColumnIndex);
+ tokenStream->srcCoords.lineNumAndColumnIndex(pos->end, &endLineNum, &endColumnIndex);
+
+ if (!newObject(&to))
+ return false;
+ val.setObject(*to);
+ if (!defineProperty(loc, "start", val))
+ return false;
+ val.setNumber(startLineNum);
+ if (!defineProperty(to, "line", val))
+ return false;
+ val.setNumber(startColumnIndex);
+ if (!defineProperty(to, "column", val))
+ return false;
+
+ if (!newObject(&to))
+ return false;
+ val.setObject(*to);
+ if (!defineProperty(loc, "end", val))
+ return false;
+ val.setNumber(endLineNum);
+ if (!defineProperty(to, "line", val))
+ return false;
+ val.setNumber(endColumnIndex);
+ if (!defineProperty(to, "column", val))
+ return false;
+
+ if (!defineProperty(loc, "source", srcval))
+ return false;
+
+ return true;
+}
+
+bool
+NodeBuilder::setNodeLoc(HandleObject node, TokenPos* pos)
+{
+ if (!saveLoc) {
+ RootedValue nullVal(cx, NullValue());
+ return defineProperty(node, "loc", nullVal);
+ }
+
+ RootedValue loc(cx);
+ return newNodeLoc(pos, &loc) &&
+ defineProperty(node, "loc", loc);
+}
+
+bool
+NodeBuilder::program(NodeVector& elts, TokenPos* pos, MutableHandleValue dst)
+{
+ return listNode(AST_PROGRAM, "body", elts, pos, dst);
+}
+
+bool
+NodeBuilder::blockStatement(NodeVector& elts, TokenPos* pos, MutableHandleValue dst)
+{
+ return listNode(AST_BLOCK_STMT, "body", elts, pos, dst);
+}
+
+bool
+NodeBuilder::expressionStatement(HandleValue expr, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_EXPR_STMT]);
+ if (!cb.isNull())
+ return callback(cb, expr, pos, dst);
+
+ return newNode(AST_EXPR_STMT, pos, "expression", expr, dst);
+}
+
+bool
+NodeBuilder::emptyStatement(TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_EMPTY_STMT]);
+ if (!cb.isNull())
+ return callback(cb, pos, dst);
+
+ return newNode(AST_EMPTY_STMT, pos, dst);
+}
+
+bool
+NodeBuilder::ifStatement(HandleValue test, HandleValue cons, HandleValue alt, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_IF_STMT]);
+ if (!cb.isNull())
+ return callback(cb, test, cons, opt(alt), pos, dst);
+
+ return newNode(AST_IF_STMT, pos,
+ "test", test,
+ "consequent", cons,
+ "alternate", alt,
+ dst);
+}
+
+bool
+NodeBuilder::breakStatement(HandleValue label, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_BREAK_STMT]);
+ if (!cb.isNull())
+ return callback(cb, opt(label), pos, dst);
+
+ return newNode(AST_BREAK_STMT, pos, "label", label, dst);
+}
+
+bool
+NodeBuilder::continueStatement(HandleValue label, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_CONTINUE_STMT]);
+ if (!cb.isNull())
+ return callback(cb, opt(label), pos, dst);
+
+ return newNode(AST_CONTINUE_STMT, pos, "label", label, dst);
+}
+
+bool
+NodeBuilder::labeledStatement(HandleValue label, HandleValue stmt, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_LAB_STMT]);
+ if (!cb.isNull())
+ return callback(cb, label, stmt, pos, dst);
+
+ return newNode(AST_LAB_STMT, pos,
+ "label", label,
+ "body", stmt,
+ dst);
+}
+
+bool
+NodeBuilder::throwStatement(HandleValue arg, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_THROW_STMT]);
+ if (!cb.isNull())
+ return callback(cb, arg, pos, dst);
+
+ return newNode(AST_THROW_STMT, pos, "argument", arg, dst);
+}
+
+bool
+NodeBuilder::returnStatement(HandleValue arg, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_RETURN_STMT]);
+ if (!cb.isNull())
+ return callback(cb, opt(arg), pos, dst);
+
+ return newNode(AST_RETURN_STMT, pos, "argument", arg, dst);
+}
+
+bool
+NodeBuilder::forStatement(HandleValue init, HandleValue test, HandleValue update, HandleValue stmt,
+ TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_FOR_STMT]);
+ if (!cb.isNull())
+ return callback(cb, opt(init), opt(test), opt(update), stmt, pos, dst);
+
+ return newNode(AST_FOR_STMT, pos,
+ "init", init,
+ "test", test,
+ "update", update,
+ "body", stmt,
+ dst);
+}
+
+bool
+NodeBuilder::forInStatement(HandleValue var, HandleValue expr, HandleValue stmt, bool isForEach,
+ TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue isForEachVal(cx, BooleanValue(isForEach));
+
+ RootedValue cb(cx, callbacks[AST_FOR_IN_STMT]);
+ if (!cb.isNull())
+ return callback(cb, var, expr, stmt, isForEachVal, pos, dst);
+
+ return newNode(AST_FOR_IN_STMT, pos,
+ "left", var,
+ "right", expr,
+ "body", stmt,
+ "each", isForEachVal,
+ dst);
+}
+
+bool
+NodeBuilder::forOfStatement(HandleValue var, HandleValue expr, HandleValue stmt, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_FOR_OF_STMT]);
+ if (!cb.isNull())
+ return callback(cb, var, expr, stmt, pos, dst);
+
+ return newNode(AST_FOR_OF_STMT, pos,
+ "left", var,
+ "right", expr,
+ "body", stmt,
+ dst);
+}
+
+bool
+NodeBuilder::withStatement(HandleValue expr, HandleValue stmt, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_WITH_STMT]);
+ if (!cb.isNull())
+ return callback(cb, expr, stmt, pos, dst);
+
+ return newNode(AST_WITH_STMT, pos,
+ "object", expr,
+ "body", stmt,
+ dst);
+}
+
+bool
+NodeBuilder::whileStatement(HandleValue test, HandleValue stmt, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_WHILE_STMT]);
+ if (!cb.isNull())
+ return callback(cb, test, stmt, pos, dst);
+
+ return newNode(AST_WHILE_STMT, pos,
+ "test", test,
+ "body", stmt,
+ dst);
+}
+
+bool
+NodeBuilder::doWhileStatement(HandleValue stmt, HandleValue test, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_DO_STMT]);
+ if (!cb.isNull())
+ return callback(cb, stmt, test, pos, dst);
+
+ return newNode(AST_DO_STMT, pos,
+ "body", stmt,
+ "test", test,
+ dst);
+}
+
+bool
+NodeBuilder::switchStatement(HandleValue disc, NodeVector& elts, bool lexical, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue array(cx);
+ if (!newArray(elts, &array))
+ return false;
+
+ RootedValue lexicalVal(cx, BooleanValue(lexical));
+
+ RootedValue cb(cx, callbacks[AST_SWITCH_STMT]);
+ if (!cb.isNull())
+ return callback(cb, disc, array, lexicalVal, pos, dst);
+
+ return newNode(AST_SWITCH_STMT, pos,
+ "discriminant", disc,
+ "cases", array,
+ "lexical", lexicalVal,
+ dst);
+}
+
+bool
+NodeBuilder::tryStatement(HandleValue body, NodeVector& guarded, HandleValue unguarded,
+ HandleValue finally, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue guardedHandlers(cx);
+ if (!newArray(guarded, &guardedHandlers))
+ return false;
+
+ RootedValue cb(cx, callbacks[AST_TRY_STMT]);
+ if (!cb.isNull())
+ return callback(cb, body, guardedHandlers, unguarded, opt(finally), pos, dst);
+
+ return newNode(AST_TRY_STMT, pos,
+ "block", body,
+ "guardedHandlers", guardedHandlers,
+ "handler", unguarded,
+ "finalizer", finally,
+ dst);
+}
+
+bool
+NodeBuilder::debuggerStatement(TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_DEBUGGER_STMT]);
+ if (!cb.isNull())
+ return callback(cb, pos, dst);
+
+ return newNode(AST_DEBUGGER_STMT, pos, dst);
+}
+
+bool
+NodeBuilder::binaryExpression(BinaryOperator op, HandleValue left, HandleValue right, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ MOZ_ASSERT(op > BINOP_ERR && op < BINOP_LIMIT);
+
+ RootedValue opName(cx);
+ if (!atomValue(binopNames[op], &opName))
+ return false;
+
+ RootedValue cb(cx, callbacks[AST_BINARY_EXPR]);
+ if (!cb.isNull())
+ return callback(cb, opName, left, right, pos, dst);
+
+ return newNode(AST_BINARY_EXPR, pos,
+ "operator", opName,
+ "left", left,
+ "right", right,
+ dst);
+}
+
+bool
+NodeBuilder::unaryExpression(UnaryOperator unop, HandleValue expr, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ MOZ_ASSERT(unop > UNOP_ERR && unop < UNOP_LIMIT);
+
+ RootedValue opName(cx);
+ if (!atomValue(unopNames[unop], &opName))
+ return false;
+
+ RootedValue cb(cx, callbacks[AST_UNARY_EXPR]);
+ if (!cb.isNull())
+ return callback(cb, opName, expr, pos, dst);
+
+ RootedValue trueVal(cx, BooleanValue(true));
+ return newNode(AST_UNARY_EXPR, pos,
+ "operator", opName,
+ "argument", expr,
+ "prefix", trueVal,
+ dst);
+}
+
+bool
+NodeBuilder::assignmentExpression(AssignmentOperator aop, HandleValue lhs, HandleValue rhs,
+ TokenPos* pos, MutableHandleValue dst)
+{
+ MOZ_ASSERT(aop > AOP_ERR && aop < AOP_LIMIT);
+
+ RootedValue opName(cx);
+ if (!atomValue(aopNames[aop], &opName))
+ return false;
+
+ RootedValue cb(cx, callbacks[AST_ASSIGN_EXPR]);
+ if (!cb.isNull())
+ return callback(cb, opName, lhs, rhs, pos, dst);
+
+ return newNode(AST_ASSIGN_EXPR, pos,
+ "operator", opName,
+ "left", lhs,
+ "right", rhs,
+ dst);
+}
+
+bool
+NodeBuilder::updateExpression(HandleValue expr, bool incr, bool prefix, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue opName(cx);
+ if (!atomValue(incr ? "++" : "--", &opName))
+ return false;
+
+ RootedValue prefixVal(cx, BooleanValue(prefix));
+
+ RootedValue cb(cx, callbacks[AST_UPDATE_EXPR]);
+ if (!cb.isNull())
+ return callback(cb, expr, opName, prefixVal, pos, dst);
+
+ return newNode(AST_UPDATE_EXPR, pos,
+ "operator", opName,
+ "argument", expr,
+ "prefix", prefixVal,
+ dst);
+}
+
+bool
+NodeBuilder::logicalExpression(bool lor, HandleValue left, HandleValue right, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue opName(cx);
+ if (!atomValue(lor ? "||" : "&&", &opName))
+ return false;
+
+ RootedValue cb(cx, callbacks[AST_LOGICAL_EXPR]);
+ if (!cb.isNull())
+ return callback(cb, opName, left, right, pos, dst);
+
+ return newNode(AST_LOGICAL_EXPR, pos,
+ "operator", opName,
+ "left", left,
+ "right", right,
+ dst);
+}
+
+bool
+NodeBuilder::conditionalExpression(HandleValue test, HandleValue cons, HandleValue alt,
+ TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_COND_EXPR]);
+ if (!cb.isNull())
+ return callback(cb, test, cons, alt, pos, dst);
+
+ return newNode(AST_COND_EXPR, pos,
+ "test", test,
+ "consequent", cons,
+ "alternate", alt,
+ dst);
+}
+
+bool
+NodeBuilder::sequenceExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst)
+{
+ return listNode(AST_LIST_EXPR, "expressions", elts, pos, dst);
+}
+
+bool
+NodeBuilder::callExpression(HandleValue callee, NodeVector& args, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue array(cx);
+ if (!newArray(args, &array))
+ return false;
+
+ RootedValue cb(cx, callbacks[AST_CALL_EXPR]);
+ if (!cb.isNull())
+ return callback(cb, callee, array, pos, dst);
+
+ return newNode(AST_CALL_EXPR, pos,
+ "callee", callee,
+ "arguments", array,
+ dst);
+}
+
+bool
+NodeBuilder::newExpression(HandleValue callee, NodeVector& args, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue array(cx);
+ if (!newArray(args, &array))
+ return false;
+
+ RootedValue cb(cx, callbacks[AST_NEW_EXPR]);
+ if (!cb.isNull())
+ return callback(cb, callee, array, pos, dst);
+
+ return newNode(AST_NEW_EXPR, pos,
+ "callee", callee,
+ "arguments", array,
+ dst);
+}
+
+bool
+NodeBuilder::memberExpression(bool computed, HandleValue expr, HandleValue member, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue computedVal(cx, BooleanValue(computed));
+
+ RootedValue cb(cx, callbacks[AST_MEMBER_EXPR]);
+ if (!cb.isNull())
+ return callback(cb, computedVal, expr, member, pos, dst);
+
+ return newNode(AST_MEMBER_EXPR, pos,
+ "object", expr,
+ "property", member,
+ "computed", computedVal,
+ dst);
+}
+
+bool
+NodeBuilder::arrayExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst)
+{
+ return listNode(AST_ARRAY_EXPR, "elements", elts, pos, dst);
+}
+
+bool
+NodeBuilder::callSiteObj(NodeVector& raw, NodeVector& cooked, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue rawVal(cx);
+ if (!newArray(raw, &rawVal))
+ return false;
+
+ RootedValue cookedVal(cx);
+ if (!newArray(cooked, &cookedVal))
+ return false;
+
+ return newNode(AST_CALL_SITE_OBJ, pos,
+ "raw", rawVal,
+ "cooked", cookedVal,
+ dst);
+}
+
+bool
+NodeBuilder::taggedTemplate(HandleValue callee, NodeVector& args, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue array(cx);
+ if (!newArray(args, &array))
+ return false;
+
+ return newNode(AST_TAGGED_TEMPLATE, pos,
+ "callee", callee,
+ "arguments", array,
+ dst);
+}
+
+bool
+NodeBuilder::templateLiteral(NodeVector& elts, TokenPos* pos, MutableHandleValue dst)
+{
+ return listNode(AST_TEMPLATE_LITERAL, "elements", elts, pos, dst);
+}
+
+bool
+NodeBuilder::computedName(HandleValue name, TokenPos* pos, MutableHandleValue dst)
+{
+ return newNode(AST_COMPUTED_NAME, pos,
+ "name", name,
+ dst);
+}
+
+bool
+NodeBuilder::spreadExpression(HandleValue expr, TokenPos* pos, MutableHandleValue dst)
+{
+ return newNode(AST_SPREAD_EXPR, pos,
+ "expression", expr,
+ dst);
+}
+
+bool
+NodeBuilder::propertyPattern(HandleValue key, HandleValue patt, bool isShorthand, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue kindName(cx);
+ if (!atomValue("init", &kindName))
+ return false;
+
+ RootedValue isShorthandVal(cx, BooleanValue(isShorthand));
+
+ RootedValue cb(cx, callbacks[AST_PROP_PATT]);
+ if (!cb.isNull())
+ return callback(cb, key, patt, pos, dst);
+
+ return newNode(AST_PROP_PATT, pos,
+ "key", key,
+ "value", patt,
+ "kind", kindName,
+ "shorthand", isShorthandVal,
+ dst);
+}
+
+bool
+NodeBuilder::prototypeMutation(HandleValue val, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_PROTOTYPEMUTATION]);
+ if (!cb.isNull())
+ return callback(cb, val, pos, dst);
+
+ return newNode(AST_PROTOTYPEMUTATION, pos,
+ "value", val,
+ dst);
+}
+
+bool
+NodeBuilder::propertyInitializer(HandleValue key, HandleValue val, PropKind kind, bool isShorthand,
+ bool isMethod, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue kindName(cx);
+ if (!atomValue(kind == PROP_INIT
+ ? "init"
+ : kind == PROP_GETTER
+ ? "get"
+ : "set", &kindName)) {
+ return false;
+ }
+
+ RootedValue isShorthandVal(cx, BooleanValue(isShorthand));
+ RootedValue isMethodVal(cx, BooleanValue(isMethod));
+
+ RootedValue cb(cx, callbacks[AST_PROPERTY]);
+ if (!cb.isNull())
+ return callback(cb, kindName, key, val, pos, dst);
+
+ return newNode(AST_PROPERTY, pos,
+ "key", key,
+ "value", val,
+ "kind", kindName,
+ "method", isMethodVal,
+ "shorthand", isShorthandVal,
+ dst);
+}
+
+bool
+NodeBuilder::objectExpression(NodeVector& elts, TokenPos* pos, MutableHandleValue dst)
+{
+ return listNode(AST_OBJECT_EXPR, "properties", elts, pos, dst);
+}
+
+bool
+NodeBuilder::thisExpression(TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_THIS_EXPR]);
+ if (!cb.isNull())
+ return callback(cb, pos, dst);
+
+ return newNode(AST_THIS_EXPR, pos, dst);
+}
+
+bool
+NodeBuilder::yieldExpression(HandleValue arg, YieldKind kind, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_YIELD_EXPR]);
+ RootedValue delegateVal(cx);
+
+ switch (kind) {
+ case Delegating:
+ delegateVal = BooleanValue(true);
+ break;
+ case NotDelegating:
+ delegateVal = BooleanValue(false);
+ break;
+ }
+
+ if (!cb.isNull())
+ return callback(cb, opt(arg), delegateVal, pos, dst);
+ return newNode(AST_YIELD_EXPR, pos, "argument", arg, "delegate", delegateVal, dst);
+}
+
+bool
+NodeBuilder::comprehensionBlock(HandleValue patt, HandleValue src, bool isForEach, bool isForOf, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue isForEachVal(cx, BooleanValue(isForEach));
+ RootedValue isForOfVal(cx, BooleanValue(isForOf));
+
+ RootedValue cb(cx, callbacks[AST_COMP_BLOCK]);
+ if (!cb.isNull())
+ return callback(cb, patt, src, isForEachVal, isForOfVal, pos, dst);
+
+ return newNode(AST_COMP_BLOCK, pos,
+ "left", patt,
+ "right", src,
+ "each", isForEachVal,
+ "of", isForOfVal,
+ dst);
+}
+
+bool
+NodeBuilder::comprehensionIf(HandleValue test, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_COMP_IF]);
+ if (!cb.isNull())
+ return callback(cb, test, pos, dst);
+
+ return newNode(AST_COMP_IF, pos,
+ "test", test,
+ dst);
+}
+
+bool
+NodeBuilder::comprehensionExpression(HandleValue body, NodeVector& blocks, HandleValue filter,
+ bool isLegacy, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue array(cx);
+ if (!newArray(blocks, &array))
+ return false;
+
+ RootedValue style(cx);
+ if (!atomValue(isLegacy ? "legacy" : "modern", &style))
+ return false;
+
+ RootedValue cb(cx, callbacks[AST_COMP_EXPR]);
+ if (!cb.isNull())
+ return callback(cb, body, array, opt(filter), style, pos, dst);
+
+ return newNode(AST_COMP_EXPR, pos,
+ "body", body,
+ "blocks", array,
+ "filter", filter,
+ "style", style,
+ dst);
+}
+
+bool
+NodeBuilder::generatorExpression(HandleValue body, NodeVector& blocks, HandleValue filter,
+ bool isLegacy, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue array(cx);
+ if (!newArray(blocks, &array))
+ return false;
+
+ RootedValue style(cx);
+ if (!atomValue(isLegacy ? "legacy" : "modern", &style))
+ return false;
+
+ RootedValue cb(cx, callbacks[AST_GENERATOR_EXPR]);
+ if (!cb.isNull())
+ return callback(cb, body, array, opt(filter), style, pos, dst);
+
+ return newNode(AST_GENERATOR_EXPR, pos,
+ "body", body,
+ "blocks", array,
+ "filter", filter,
+ "style", style,
+ dst);
+}
+
+bool
+NodeBuilder::importDeclaration(NodeVector& elts, HandleValue moduleSpec, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue array(cx);
+ if (!newArray(elts, &array))
+ return false;
+
+ RootedValue cb(cx, callbacks[AST_IMPORT_DECL]);
+ if (!cb.isNull())
+ return callback(cb, array, moduleSpec, pos, dst);
+
+ return newNode(AST_IMPORT_DECL, pos,
+ "specifiers", array,
+ "source", moduleSpec,
+ dst);
+}
+
+bool
+NodeBuilder::importSpecifier(HandleValue importName, HandleValue bindingName, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_IMPORT_SPEC]);
+ if (!cb.isNull())
+ return callback(cb, importName, bindingName, pos, dst);
+
+ return newNode(AST_IMPORT_SPEC, pos,
+ "id", importName,
+ "name", bindingName,
+ dst);
+}
+
+bool
+NodeBuilder::exportDeclaration(HandleValue decl, NodeVector& elts, HandleValue moduleSpec,
+ HandleValue isDefault, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue array(cx, NullValue());
+ if (decl.isNull() && !newArray(elts, &array))
+ return false;
+
+ RootedValue cb(cx, callbacks[AST_EXPORT_DECL]);
+
+ if (!cb.isNull())
+ return callback(cb, decl, array, moduleSpec, pos, dst);
+
+ return newNode(AST_EXPORT_DECL, pos,
+ "declaration", decl,
+ "specifiers", array,
+ "source", moduleSpec,
+ "isDefault", isDefault,
+ dst);
+}
+
+bool
+NodeBuilder::exportSpecifier(HandleValue bindingName, HandleValue exportName, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_EXPORT_SPEC]);
+ if (!cb.isNull())
+ return callback(cb, bindingName, exportName, pos, dst);
+
+ return newNode(AST_EXPORT_SPEC, pos,
+ "id", bindingName,
+ "name", exportName,
+ dst);
+}
+
+bool
+NodeBuilder::exportBatchSpecifier(TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_EXPORT_BATCH_SPEC]);
+ if (!cb.isNull())
+ return callback(cb, pos, dst);
+
+ return newNode(AST_EXPORT_BATCH_SPEC, pos, dst);
+}
+
+bool
+NodeBuilder::variableDeclaration(NodeVector& elts, VarDeclKind kind, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ MOZ_ASSERT(kind > VARDECL_ERR && kind < VARDECL_LIMIT);
+
+ RootedValue array(cx), kindName(cx);
+ if (!newArray(elts, &array) ||
+ !atomValue(kind == VARDECL_CONST
+ ? "const"
+ : kind == VARDECL_LET
+ ? "let"
+ : "var", &kindName)) {
+ return false;
+ }
+
+ RootedValue cb(cx, callbacks[AST_VAR_DECL]);
+ if (!cb.isNull())
+ return callback(cb, kindName, array, pos, dst);
+
+ return newNode(AST_VAR_DECL, pos,
+ "kind", kindName,
+ "declarations", array,
+ dst);
+}
+
+bool
+NodeBuilder::variableDeclarator(HandleValue id, HandleValue init, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_VAR_DTOR]);
+ if (!cb.isNull())
+ return callback(cb, id, opt(init), pos, dst);
+
+ return newNode(AST_VAR_DTOR, pos, "id", id, "init", init, dst);
+}
+
+bool
+NodeBuilder::switchCase(HandleValue expr, NodeVector& elts, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue array(cx);
+ if (!newArray(elts, &array))
+ return false;
+
+ RootedValue cb(cx, callbacks[AST_CASE]);
+ if (!cb.isNull())
+ return callback(cb, opt(expr), array, pos, dst);
+
+ return newNode(AST_CASE, pos,
+ "test", expr,
+ "consequent", array,
+ dst);
+}
+
+bool
+NodeBuilder::catchClause(HandleValue var, HandleValue guard, HandleValue body, TokenPos* pos,
+ MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_CATCH]);
+ if (!cb.isNull())
+ return callback(cb, var, opt(guard), body, pos, dst);
+
+ return newNode(AST_CATCH, pos,
+ "param", var,
+ "guard", guard,
+ "body", body,
+ dst);
+}
+
+bool
+NodeBuilder::literal(HandleValue val, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_LITERAL]);
+ if (!cb.isNull())
+ return callback(cb, val, pos, dst);
+
+ return newNode(AST_LITERAL, pos, "value", val, dst);
+}
+
+bool
+NodeBuilder::identifier(HandleValue name, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_IDENTIFIER]);
+ if (!cb.isNull())
+ return callback(cb, name, pos, dst);
+
+ return newNode(AST_IDENTIFIER, pos, "name", name, dst);
+}
+
+bool
+NodeBuilder::objectPattern(NodeVector& elts, TokenPos* pos, MutableHandleValue dst)
+{
+ return listNode(AST_OBJECT_PATT, "properties", elts, pos, dst);
+}
+
+bool
+NodeBuilder::arrayPattern(NodeVector& elts, TokenPos* pos, MutableHandleValue dst)
+{
+ return listNode(AST_ARRAY_PATT, "elements", elts, pos, dst);
+}
+
+bool
+NodeBuilder::function(ASTType type, TokenPos* pos,
+ HandleValue id, NodeVector& args, NodeVector& defaults,
+ HandleValue body, HandleValue rest,
+ GeneratorStyle generatorStyle, bool isAsync, bool isExpression,
+ MutableHandleValue dst)
+{
+ RootedValue array(cx), defarray(cx);
+ if (!newArray(args, &array))
+ return false;
+ if (!newArray(defaults, &defarray))
+ return false;
+
+ bool isGenerator = generatorStyle != GeneratorStyle::None;
+ RootedValue isGeneratorVal(cx, BooleanValue(isGenerator));
+ RootedValue isAsyncVal(cx, BooleanValue(isAsync));
+ RootedValue isExpressionVal(cx, BooleanValue(isExpression));
+
+ RootedValue cb(cx, callbacks[type]);
+ if (!cb.isNull()) {
+ return callback(cb, opt(id), array, body, isGeneratorVal, isExpressionVal, pos, dst);
+ }
+
+ if (isGenerator) {
+ // Distinguish ES6 generators from legacy generators.
+ RootedValue styleVal(cx);
+ JSAtom* styleStr = generatorStyle == GeneratorStyle::ES6
+ ? Atomize(cx, "es6", 3)
+ : Atomize(cx, "legacy", 6);
+ if (!styleStr)
+ return false;
+ styleVal.setString(styleStr);
+ return newNode(type, pos,
+ "id", id,
+ "params", array,
+ "defaults", defarray,
+ "body", body,
+ "rest", rest,
+ "generator", isGeneratorVal,
+ "async", isAsyncVal,
+ "style", styleVal,
+ "expression", isExpressionVal,
+ dst);
+ }
+
+ return newNode(type, pos,
+ "id", id,
+ "params", array,
+ "defaults", defarray,
+ "body", body,
+ "rest", rest,
+ "generator", isGeneratorVal,
+ "async", isAsyncVal,
+ "expression", isExpressionVal,
+ dst);
+}
+
+bool
+NodeBuilder::classMethod(HandleValue name, HandleValue body, PropKind kind, bool isStatic,
+ TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue kindName(cx);
+ if (!atomValue(kind == PROP_INIT
+ ? "method"
+ : kind == PROP_GETTER
+ ? "get"
+ : "set", &kindName)) {
+ return false;
+ }
+
+ RootedValue isStaticVal(cx, BooleanValue(isStatic));
+ RootedValue cb(cx, callbacks[AST_CLASS_METHOD]);
+ if (!cb.isNull())
+ return callback(cb, kindName, name, body, isStaticVal, pos, dst);
+
+ return newNode(AST_CLASS_METHOD, pos,
+ "name", name,
+ "body", body,
+ "kind", kindName,
+ "static", isStaticVal,
+ dst);
+}
+
+bool
+NodeBuilder::classMethods(NodeVector& methods, MutableHandleValue dst)
+{
+ return newArray(methods, dst);
+}
+
+bool
+NodeBuilder::classDefinition(bool expr, HandleValue name, HandleValue heritage, HandleValue block,
+ TokenPos* pos, MutableHandleValue dst)
+{
+ ASTType type = expr ? AST_CLASS_EXPR : AST_CLASS_STMT;
+ RootedValue cb(cx, callbacks[type]);
+ if (!cb.isNull())
+ return callback(cb, name, heritage, block, pos, dst);
+
+ return newNode(type, pos,
+ "id", name,
+ "superClass", heritage,
+ "body", block,
+ dst);
+}
+
+bool
+NodeBuilder::metaProperty(HandleValue meta, HandleValue property, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_METAPROPERTY]);
+ if (!cb.isNull())
+ return callback(cb, meta, property, pos, dst);
+
+ return newNode(AST_METAPROPERTY, pos,
+ "meta", meta,
+ "property", property,
+ dst);
+}
+
+bool
+NodeBuilder::super(TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue cb(cx, callbacks[AST_SUPER]);
+ if (!cb.isNull())
+ return callback(cb, pos, dst);
+
+ return newNode(AST_SUPER, pos, dst);
+}
+
+namespace {
+
+/*
+ * Serialization of parse nodes to JavaScript objects.
+ *
+ * All serialization methods take a non-nullable ParseNode pointer.
+ */
+class ASTSerializer
+{
+ JSContext* cx;
+ Parser<FullParseHandler>* parser;
+ NodeBuilder builder;
+ DebugOnly<uint32_t> lineno;
+
+ Value unrootedAtomContents(JSAtom* atom) {
+ return StringValue(atom ? atom : cx->names().empty);
+ }
+
+ BinaryOperator binop(ParseNodeKind kind, JSOp op);
+ UnaryOperator unop(ParseNodeKind kind, JSOp op);
+ AssignmentOperator aop(JSOp op);
+
+ bool statements(ParseNode* pn, NodeVector& elts);
+ bool expressions(ParseNode* pn, NodeVector& elts);
+ bool leftAssociate(ParseNode* pn, MutableHandleValue dst);
+ bool rightAssociate(ParseNode* pn, MutableHandleValue dst);
+ bool functionArgs(ParseNode* pn, ParseNode* pnargs,
+ NodeVector& args, NodeVector& defaults, MutableHandleValue rest);
+
+ bool sourceElement(ParseNode* pn, MutableHandleValue dst);
+
+ bool declaration(ParseNode* pn, MutableHandleValue dst);
+ bool variableDeclaration(ParseNode* pn, bool lexical, MutableHandleValue dst);
+ bool variableDeclarator(ParseNode* pn, MutableHandleValue dst);
+ bool importDeclaration(ParseNode* pn, MutableHandleValue dst);
+ bool importSpecifier(ParseNode* pn, MutableHandleValue dst);
+ bool exportDeclaration(ParseNode* pn, MutableHandleValue dst);
+ bool exportSpecifier(ParseNode* pn, MutableHandleValue dst);
+ bool classDefinition(ParseNode* pn, bool expr, MutableHandleValue dst);
+
+ bool optStatement(ParseNode* pn, MutableHandleValue dst) {
+ if (!pn) {
+ dst.setMagic(JS_SERIALIZE_NO_NODE);
+ return true;
+ }
+ return statement(pn, dst);
+ }
+
+ bool forInit(ParseNode* pn, MutableHandleValue dst);
+ bool forIn(ParseNode* loop, ParseNode* head, HandleValue var, HandleValue stmt,
+ MutableHandleValue dst);
+ bool forOf(ParseNode* loop, ParseNode* head, HandleValue var, HandleValue stmt,
+ MutableHandleValue dst);
+ bool statement(ParseNode* pn, MutableHandleValue dst);
+ bool blockStatement(ParseNode* pn, MutableHandleValue dst);
+ bool switchStatement(ParseNode* pn, MutableHandleValue dst);
+ bool switchCase(ParseNode* pn, MutableHandleValue dst);
+ bool tryStatement(ParseNode* pn, MutableHandleValue dst);
+ bool catchClause(ParseNode* pn, bool* isGuarded, MutableHandleValue dst);
+
+ bool optExpression(ParseNode* pn, MutableHandleValue dst) {
+ if (!pn) {
+ dst.setMagic(JS_SERIALIZE_NO_NODE);
+ return true;
+ }
+ return expression(pn, dst);
+ }
+
+ bool expression(ParseNode* pn, MutableHandleValue dst);
+
+ bool propertyName(ParseNode* pn, MutableHandleValue dst);
+ bool property(ParseNode* pn, MutableHandleValue dst);
+
+ bool classMethod(ParseNode* pn, MutableHandleValue dst);
+
+ bool optIdentifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst) {
+ if (!atom) {
+ dst.setMagic(JS_SERIALIZE_NO_NODE);
+ return true;
+ }
+ return identifier(atom, pos, dst);
+ }
+
+ bool identifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst);
+ bool identifier(ParseNode* pn, MutableHandleValue dst);
+ bool literal(ParseNode* pn, MutableHandleValue dst);
+
+ bool pattern(ParseNode* pn, MutableHandleValue dst);
+ bool arrayPattern(ParseNode* pn, MutableHandleValue dst);
+ bool objectPattern(ParseNode* pn, MutableHandleValue dst);
+
+ bool function(ParseNode* pn, ASTType type, MutableHandleValue dst);
+ bool functionArgsAndBody(ParseNode* pn, NodeVector& args, NodeVector& defaults,
+ bool isAsync, bool isExpression,
+ MutableHandleValue body, MutableHandleValue rest);
+ bool functionBody(ParseNode* pn, TokenPos* pos, MutableHandleValue dst);
+
+ bool comprehensionBlock(ParseNode* pn, MutableHandleValue dst);
+ bool comprehensionIf(ParseNode* pn, MutableHandleValue dst);
+ bool comprehension(ParseNode* pn, MutableHandleValue dst);
+ bool generatorExpression(ParseNode* pn, MutableHandleValue dst);
+
+ public:
+ ASTSerializer(JSContext* c, bool l, char const* src, uint32_t ln)
+ : cx(c)
+ , parser(nullptr)
+ , builder(c, l, src)
+#ifdef DEBUG
+ , lineno(ln)
+#endif
+ {}
+
+ bool init(HandleObject userobj) {
+ return builder.init(userobj);
+ }
+
+ void setParser(Parser<FullParseHandler>* p) {
+ parser = p;
+ builder.setTokenStream(&p->tokenStream);
+ }
+
+ bool program(ParseNode* pn, MutableHandleValue dst);
+};
+
+} /* anonymous namespace */
+
+AssignmentOperator
+ASTSerializer::aop(JSOp op)
+{
+ switch (op) {
+ case JSOP_NOP:
+ return AOP_ASSIGN;
+ case JSOP_ADD:
+ return AOP_PLUS;
+ case JSOP_SUB:
+ return AOP_MINUS;
+ case JSOP_MUL:
+ return AOP_STAR;
+ case JSOP_DIV:
+ return AOP_DIV;
+ case JSOP_MOD:
+ return AOP_MOD;
+ case JSOP_POW:
+ return AOP_POW;
+ case JSOP_LSH:
+ return AOP_LSH;
+ case JSOP_RSH:
+ return AOP_RSH;
+ case JSOP_URSH:
+ return AOP_URSH;
+ case JSOP_BITOR:
+ return AOP_BITOR;
+ case JSOP_BITXOR:
+ return AOP_BITXOR;
+ case JSOP_BITAND:
+ return AOP_BITAND;
+ default:
+ return AOP_ERR;
+ }
+}
+
+UnaryOperator
+ASTSerializer::unop(ParseNodeKind kind, JSOp op)
+{
+ if (IsDeleteKind(kind))
+ return UNOP_DELETE;
+
+ if (IsTypeofKind(kind))
+ return UNOP_TYPEOF;
+
+ if (kind == PNK_AWAIT)
+ return UNOP_AWAIT;
+
+ switch (op) {
+ case JSOP_NEG:
+ return UNOP_NEG;
+ case JSOP_POS:
+ return UNOP_POS;
+ case JSOP_NOT:
+ return UNOP_NOT;
+ case JSOP_BITNOT:
+ return UNOP_BITNOT;
+ case JSOP_VOID:
+ return UNOP_VOID;
+ default:
+ return UNOP_ERR;
+ }
+}
+
+BinaryOperator
+ASTSerializer::binop(ParseNodeKind kind, JSOp op)
+{
+ switch (kind) {
+ case PNK_LSH:
+ return BINOP_LSH;
+ case PNK_RSH:
+ return BINOP_RSH;
+ case PNK_URSH:
+ return BINOP_URSH;
+ case PNK_LT:
+ return BINOP_LT;
+ case PNK_LE:
+ return BINOP_LE;
+ case PNK_GT:
+ return BINOP_GT;
+ case PNK_GE:
+ return BINOP_GE;
+ case PNK_EQ:
+ return BINOP_EQ;
+ case PNK_NE:
+ return BINOP_NE;
+ case PNK_STRICTEQ:
+ return BINOP_STRICTEQ;
+ case PNK_STRICTNE:
+ return BINOP_STRICTNE;
+ case PNK_ADD:
+ return BINOP_ADD;
+ case PNK_SUB:
+ return BINOP_SUB;
+ case PNK_STAR:
+ return BINOP_STAR;
+ case PNK_DIV:
+ return BINOP_DIV;
+ case PNK_MOD:
+ return BINOP_MOD;
+ case PNK_POW:
+ return BINOP_POW;
+ case PNK_BITOR:
+ return BINOP_BITOR;
+ case PNK_BITXOR:
+ return BINOP_BITXOR;
+ case PNK_BITAND:
+ return BINOP_BITAND;
+ case PNK_IN:
+ return BINOP_IN;
+ case PNK_INSTANCEOF:
+ return BINOP_INSTANCEOF;
+ default:
+ return BINOP_ERR;
+ }
+}
+
+bool
+ASTSerializer::statements(ParseNode* pn, NodeVector& elts)
+{
+ MOZ_ASSERT(pn->isKind(PNK_STATEMENTLIST));
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+
+ if (!elts.reserve(pn->pn_count))
+ return false;
+
+ for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
+ MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+
+ RootedValue elt(cx);
+ if (!sourceElement(next, &elt))
+ return false;
+ elts.infallibleAppend(elt);
+ }
+
+ return true;
+}
+
+bool
+ASTSerializer::expressions(ParseNode* pn, NodeVector& elts)
+{
+ if (!elts.reserve(pn->pn_count))
+ return false;
+
+ for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
+ MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+
+ RootedValue elt(cx);
+ if (!expression(next, &elt))
+ return false;
+ elts.infallibleAppend(elt);
+ }
+
+ return true;
+}
+
+bool
+ASTSerializer::blockStatement(ParseNode* pn, MutableHandleValue dst)
+{
+ MOZ_ASSERT(pn->isKind(PNK_STATEMENTLIST));
+
+ NodeVector stmts(cx);
+ return statements(pn, stmts) &&
+ builder.blockStatement(stmts, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::program(ParseNode* pn, MutableHandleValue dst)
+{
+ MOZ_ASSERT(parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin) == lineno);
+
+ NodeVector stmts(cx);
+ return statements(pn, stmts) &&
+ builder.program(stmts, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::sourceElement(ParseNode* pn, MutableHandleValue dst)
+{
+ /* SpiderMonkey allows declarations even in pure statement contexts. */
+ return statement(pn, dst);
+}
+
+bool
+ASTSerializer::declaration(ParseNode* pn, MutableHandleValue dst)
+{
+ MOZ_ASSERT(pn->isKind(PNK_FUNCTION) ||
+ pn->isKind(PNK_VAR) ||
+ pn->isKind(PNK_LET) ||
+ pn->isKind(PNK_CONST));
+
+ switch (pn->getKind()) {
+ case PNK_FUNCTION:
+ return function(pn, AST_FUNC_DECL, dst);
+
+ case PNK_VAR:
+ return variableDeclaration(pn, false, dst);
+
+ default:
+ MOZ_ASSERT(pn->isKind(PNK_LET) || pn->isKind(PNK_CONST));
+ return variableDeclaration(pn, true, dst);
+ }
+}
+
+bool
+ASTSerializer::variableDeclaration(ParseNode* pn, bool lexical, MutableHandleValue dst)
+{
+ MOZ_ASSERT_IF(lexical, pn->isKind(PNK_LET) || pn->isKind(PNK_CONST));
+ MOZ_ASSERT_IF(!lexical, pn->isKind(PNK_VAR));
+
+ VarDeclKind kind = VARDECL_ERR;
+ // Treat both the toplevel const binding (secretly var-like) and the lexical const
+ // the same way
+ if (lexical)
+ kind = pn->isKind(PNK_LET) ? VARDECL_LET : VARDECL_CONST;
+ else
+ kind = pn->isKind(PNK_VAR) ? VARDECL_VAR : VARDECL_CONST;
+
+ NodeVector dtors(cx);
+ if (!dtors.reserve(pn->pn_count))
+ return false;
+ for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
+ RootedValue child(cx);
+ if (!variableDeclarator(next, &child))
+ return false;
+ dtors.infallibleAppend(child);
+ }
+ return builder.variableDeclaration(dtors, kind, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::variableDeclarator(ParseNode* pn, MutableHandleValue dst)
+{
+ ParseNode* pnleft;
+ ParseNode* pnright;
+
+ if (pn->isKind(PNK_NAME)) {
+ pnleft = pn;
+ pnright = pn->pn_expr;
+ MOZ_ASSERT_IF(pnright, pn->pn_pos.encloses(pnright->pn_pos));
+ } else if (pn->isKind(PNK_ASSIGN)) {
+ pnleft = pn->pn_left;
+ pnright = pn->pn_right;
+ MOZ_ASSERT(pn->pn_pos.encloses(pnleft->pn_pos));
+ MOZ_ASSERT(pn->pn_pos.encloses(pnright->pn_pos));
+ } else {
+ /* This happens for a destructuring declarator in a for-in/of loop. */
+ pnleft = pn;
+ pnright = nullptr;
+ }
+
+ RootedValue left(cx), right(cx);
+ return pattern(pnleft, &left) &&
+ optExpression(pnright, &right) &&
+ builder.variableDeclarator(left, right, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::importDeclaration(ParseNode* pn, MutableHandleValue dst)
+{
+ MOZ_ASSERT(pn->isKind(PNK_IMPORT));
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ MOZ_ASSERT(pn->pn_left->isKind(PNK_IMPORT_SPEC_LIST));
+ MOZ_ASSERT(pn->pn_right->isKind(PNK_STRING));
+
+ NodeVector elts(cx);
+ if (!elts.reserve(pn->pn_left->pn_count))
+ return false;
+
+ for (ParseNode* next = pn->pn_left->pn_head; next; next = next->pn_next) {
+ RootedValue elt(cx);
+ if (!importSpecifier(next, &elt))
+ return false;
+ elts.infallibleAppend(elt);
+ }
+
+ RootedValue moduleSpec(cx);
+ return literal(pn->pn_right, &moduleSpec) &&
+ builder.importDeclaration(elts, moduleSpec, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::importSpecifier(ParseNode* pn, MutableHandleValue dst)
+{
+ MOZ_ASSERT(pn->isKind(PNK_IMPORT_SPEC));
+
+ RootedValue importName(cx);
+ RootedValue bindingName(cx);
+ return identifier(pn->pn_left, &importName) &&
+ identifier(pn->pn_right, &bindingName) &&
+ builder.importSpecifier(importName, bindingName, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::exportDeclaration(ParseNode* pn, MutableHandleValue dst)
+{
+ MOZ_ASSERT(pn->isKind(PNK_EXPORT) ||
+ pn->isKind(PNK_EXPORT_FROM) ||
+ pn->isKind(PNK_EXPORT_DEFAULT));
+ MOZ_ASSERT(pn->getArity() == pn->isKind(PNK_EXPORT) ? PN_UNARY : PN_BINARY);
+ MOZ_ASSERT_IF(pn->isKind(PNK_EXPORT_FROM), pn->pn_right->isKind(PNK_STRING));
+
+ RootedValue decl(cx, NullValue());
+ NodeVector elts(cx);
+
+ ParseNode* kid = pn->isKind(PNK_EXPORT) ? pn->pn_kid : pn->pn_left;
+ switch (ParseNodeKind kind = kid->getKind()) {
+ case PNK_EXPORT_SPEC_LIST:
+ if (!elts.reserve(pn->pn_left->pn_count))
+ return false;
+
+ for (ParseNode* next = pn->pn_left->pn_head; next; next = next->pn_next) {
+ RootedValue elt(cx);
+ if (next->isKind(PNK_EXPORT_SPEC)) {
+ if (!exportSpecifier(next, &elt))
+ return false;
+ } else {
+ if (!builder.exportBatchSpecifier(&pn->pn_pos, &elt))
+ return false;
+ }
+ elts.infallibleAppend(elt);
+ }
+ break;
+
+ case PNK_FUNCTION:
+ if (!function(kid, AST_FUNC_DECL, &decl))
+ return false;
+ break;
+
+ case PNK_CLASS:
+ if (!classDefinition(kid, false, &decl))
+ return false;
+ break;
+
+ case PNK_VAR:
+ case PNK_CONST:
+ case PNK_LET:
+ if (!variableDeclaration(kid, kind != PNK_VAR, &decl))
+ return false;
+ break;
+
+ default:
+ if (!expression(kid, &decl))
+ return false;
+ break;
+ }
+
+ RootedValue moduleSpec(cx, NullValue());
+ if (pn->isKind(PNK_EXPORT_FROM) && !literal(pn->pn_right, &moduleSpec))
+ return false;
+
+ RootedValue isDefault(cx, BooleanValue(false));
+ if (pn->isKind(PNK_EXPORT_DEFAULT))
+ isDefault.setBoolean(true);
+
+ return builder.exportDeclaration(decl, elts, moduleSpec, isDefault, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::exportSpecifier(ParseNode* pn, MutableHandleValue dst)
+{
+ MOZ_ASSERT(pn->isKind(PNK_EXPORT_SPEC));
+
+ RootedValue bindingName(cx);
+ RootedValue exportName(cx);
+ return identifier(pn->pn_left, &bindingName) &&
+ identifier(pn->pn_right, &exportName) &&
+ builder.exportSpecifier(bindingName, exportName, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::switchCase(ParseNode* pn, MutableHandleValue dst)
+{
+ MOZ_ASSERT_IF(pn->pn_left, pn->pn_pos.encloses(pn->pn_left->pn_pos));
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
+
+ NodeVector stmts(cx);
+
+ RootedValue expr(cx);
+
+ return optExpression(pn->as<CaseClause>().caseExpression(), &expr) &&
+ statements(pn->as<CaseClause>().statementList(), stmts) &&
+ builder.switchCase(expr, stmts, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::switchStatement(ParseNode* pn, MutableHandleValue dst)
+{
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
+
+ RootedValue disc(cx);
+
+ if (!expression(pn->pn_left, &disc))
+ return false;
+
+ ParseNode* listNode;
+ bool lexical;
+
+ if (pn->pn_right->isKind(PNK_LEXICALSCOPE)) {
+ listNode = pn->pn_right->pn_expr;
+ lexical = true;
+ } else {
+ listNode = pn->pn_right;
+ lexical = false;
+ }
+
+ NodeVector cases(cx);
+ if (!cases.reserve(listNode->pn_count))
+ return false;
+
+ for (ParseNode* next = listNode->pn_head; next; next = next->pn_next) {
+ RootedValue child(cx);
+ if (!switchCase(next, &child))
+ return false;
+ cases.infallibleAppend(child);
+ }
+
+ return builder.switchStatement(disc, cases, lexical, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::catchClause(ParseNode* pn, bool* isGuarded, MutableHandleValue dst)
+{
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid1->pn_pos));
+ MOZ_ASSERT_IF(pn->pn_kid2, pn->pn_pos.encloses(pn->pn_kid2->pn_pos));
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid3->pn_pos));
+
+ RootedValue var(cx), guard(cx), body(cx);
+
+ if (!pattern(pn->pn_kid1, &var) ||
+ !optExpression(pn->pn_kid2, &guard)) {
+ return false;
+ }
+
+ *isGuarded = !guard.isMagic(JS_SERIALIZE_NO_NODE);
+
+ return statement(pn->pn_kid3, &body) &&
+ builder.catchClause(var, guard, body, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::tryStatement(ParseNode* pn, MutableHandleValue dst)
+{
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid1->pn_pos));
+ MOZ_ASSERT_IF(pn->pn_kid2, pn->pn_pos.encloses(pn->pn_kid2->pn_pos));
+ MOZ_ASSERT_IF(pn->pn_kid3, pn->pn_pos.encloses(pn->pn_kid3->pn_pos));
+
+ RootedValue body(cx);
+ if (!statement(pn->pn_kid1, &body))
+ return false;
+
+ NodeVector guarded(cx);
+ RootedValue unguarded(cx, NullValue());
+
+ if (ParseNode* catchList = pn->pn_kid2) {
+ if (!guarded.reserve(catchList->pn_count))
+ return false;
+
+ for (ParseNode* next = catchList->pn_head; next; next = next->pn_next) {
+ RootedValue clause(cx);
+ bool isGuarded;
+ if (!catchClause(next->pn_expr, &isGuarded, &clause))
+ return false;
+ if (isGuarded)
+ guarded.infallibleAppend(clause);
+ else
+ unguarded = clause;
+ }
+ }
+
+ RootedValue finally(cx);
+ return optStatement(pn->pn_kid3, &finally) &&
+ builder.tryStatement(body, guarded, unguarded, finally, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::forInit(ParseNode* pn, MutableHandleValue dst)
+{
+ if (!pn) {
+ dst.setMagic(JS_SERIALIZE_NO_NODE);
+ return true;
+ }
+
+ bool lexical = pn->isKind(PNK_LET) || pn->isKind(PNK_CONST);
+ return (lexical || pn->isKind(PNK_VAR))
+ ? variableDeclaration(pn, lexical, dst)
+ : expression(pn, dst);
+}
+
+bool
+ASTSerializer::forOf(ParseNode* loop, ParseNode* head, HandleValue var, HandleValue stmt,
+ MutableHandleValue dst)
+{
+ RootedValue expr(cx);
+
+ return expression(head->pn_kid3, &expr) &&
+ builder.forOfStatement(var, expr, stmt, &loop->pn_pos, dst);
+}
+
+bool
+ASTSerializer::forIn(ParseNode* loop, ParseNode* head, HandleValue var, HandleValue stmt,
+ MutableHandleValue dst)
+{
+ RootedValue expr(cx);
+ bool isForEach = loop->pn_iflags & JSITER_FOREACH;
+
+ return expression(head->pn_kid3, &expr) &&
+ builder.forInStatement(var, expr, stmt, isForEach, &loop->pn_pos, dst);
+}
+
+bool
+ASTSerializer::classDefinition(ParseNode* pn, bool expr, MutableHandleValue dst)
+{
+ RootedValue className(cx, MagicValue(JS_SERIALIZE_NO_NODE));
+ RootedValue heritage(cx);
+ RootedValue classBody(cx);
+
+ if (pn->pn_kid1) {
+ if (!identifier(pn->pn_kid1->as<ClassNames>().innerBinding(), &className))
+ return false;
+ }
+
+ return optExpression(pn->pn_kid2, &heritage) &&
+ statement(pn->pn_kid3, &classBody) &&
+ builder.classDefinition(expr, className, heritage, classBody, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst)
+{
+ JS_CHECK_RECURSION(cx, return false);
+ switch (pn->getKind()) {
+ case PNK_FUNCTION:
+ case PNK_VAR:
+ return declaration(pn, dst);
+
+ case PNK_LET:
+ case PNK_CONST:
+ return declaration(pn, dst);
+
+ case PNK_IMPORT:
+ return importDeclaration(pn, dst);
+
+ case PNK_EXPORT:
+ case PNK_EXPORT_DEFAULT:
+ case PNK_EXPORT_FROM:
+ return exportDeclaration(pn, dst);
+
+ case PNK_SEMI:
+ if (pn->pn_kid) {
+ RootedValue expr(cx);
+ return expression(pn->pn_kid, &expr) &&
+ builder.expressionStatement(expr, &pn->pn_pos, dst);
+ }
+ return builder.emptyStatement(&pn->pn_pos, dst);
+
+ case PNK_LEXICALSCOPE:
+ pn = pn->pn_expr;
+ if (!pn->isKind(PNK_STATEMENTLIST))
+ return statement(pn, dst);
+ MOZ_FALLTHROUGH;
+
+ case PNK_STATEMENTLIST:
+ return blockStatement(pn, dst);
+
+ case PNK_IF:
+ {
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid1->pn_pos));
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid2->pn_pos));
+ MOZ_ASSERT_IF(pn->pn_kid3, pn->pn_pos.encloses(pn->pn_kid3->pn_pos));
+
+ RootedValue test(cx), cons(cx), alt(cx);
+
+ return expression(pn->pn_kid1, &test) &&
+ statement(pn->pn_kid2, &cons) &&
+ optStatement(pn->pn_kid3, &alt) &&
+ builder.ifStatement(test, cons, alt, &pn->pn_pos, dst);
+ }
+
+ case PNK_SWITCH:
+ return switchStatement(pn, dst);
+
+ case PNK_TRY:
+ return tryStatement(pn, dst);
+
+ case PNK_WITH:
+ case PNK_WHILE:
+ {
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
+
+ RootedValue expr(cx), stmt(cx);
+
+ return expression(pn->pn_left, &expr) &&
+ statement(pn->pn_right, &stmt) &&
+ (pn->isKind(PNK_WITH)
+ ? builder.withStatement(expr, stmt, &pn->pn_pos, dst)
+ : builder.whileStatement(expr, stmt, &pn->pn_pos, dst));
+ }
+
+ case PNK_DOWHILE:
+ {
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
+
+ RootedValue stmt(cx), test(cx);
+
+ return statement(pn->pn_left, &stmt) &&
+ expression(pn->pn_right, &test) &&
+ builder.doWhileStatement(stmt, test, &pn->pn_pos, dst);
+ }
+
+ case PNK_FOR:
+ case PNK_COMPREHENSIONFOR:
+ {
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
+
+ ParseNode* head = pn->pn_left;
+
+ MOZ_ASSERT_IF(head->pn_kid1, head->pn_pos.encloses(head->pn_kid1->pn_pos));
+ MOZ_ASSERT_IF(head->pn_kid2, head->pn_pos.encloses(head->pn_kid2->pn_pos));
+ MOZ_ASSERT_IF(head->pn_kid3, head->pn_pos.encloses(head->pn_kid3->pn_pos));
+
+ RootedValue stmt(cx);
+ if (!statement(pn->pn_right, &stmt))
+ return false;
+
+ if (head->isKind(PNK_FORIN) || head->isKind(PNK_FOROF)) {
+ RootedValue var(cx);
+ if (head->pn_kid1->isKind(PNK_LEXICALSCOPE)) {
+ if (!variableDeclaration(head->pn_kid1->pn_expr, true, &var))
+ return false;
+ } else if (!head->pn_kid1->isKind(PNK_VAR) &&
+ !head->pn_kid1->isKind(PNK_LET) &&
+ !head->pn_kid1->isKind(PNK_CONST))
+ {
+ if (!pattern(head->pn_kid1, &var))
+ return false;
+ } else {
+ if (!variableDeclaration(head->pn_kid1,
+ head->pn_kid1->isKind(PNK_LET) ||
+ head->pn_kid1->isKind(PNK_CONST),
+ &var))
+ {
+ return false;
+ }
+ }
+ if (head->isKind(PNK_FORIN))
+ return forIn(pn, head, var, stmt, dst);
+ return forOf(pn, head, var, stmt, dst);
+ }
+
+ RootedValue init(cx), test(cx), update(cx);
+
+ return forInit(head->pn_kid1, &init) &&
+ optExpression(head->pn_kid2, &test) &&
+ optExpression(head->pn_kid3, &update) &&
+ builder.forStatement(init, test, update, stmt, &pn->pn_pos, dst);
+ }
+
+ case PNK_BREAK:
+ case PNK_CONTINUE:
+ {
+ RootedValue label(cx);
+ RootedAtom pnAtom(cx, pn->pn_atom);
+ return optIdentifier(pnAtom, nullptr, &label) &&
+ (pn->isKind(PNK_BREAK)
+ ? builder.breakStatement(label, &pn->pn_pos, dst)
+ : builder.continueStatement(label, &pn->pn_pos, dst));
+ }
+
+ case PNK_LABEL:
+ {
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_expr->pn_pos));
+
+ RootedValue label(cx), stmt(cx);
+ RootedAtom pnAtom(cx, pn->as<LabeledStatement>().label());
+ return identifier(pnAtom, nullptr, &label) &&
+ statement(pn->pn_expr, &stmt) &&
+ builder.labeledStatement(label, stmt, &pn->pn_pos, dst);
+ }
+
+ case PNK_THROW:
+ {
+ MOZ_ASSERT_IF(pn->pn_kid, pn->pn_pos.encloses(pn->pn_kid->pn_pos));
+
+ RootedValue arg(cx);
+
+ return optExpression(pn->pn_kid, &arg) &&
+ builder.throwStatement(arg, &pn->pn_pos, dst);
+ }
+
+ case PNK_RETURN:
+ {
+ MOZ_ASSERT_IF(pn->pn_kid, pn->pn_pos.encloses(pn->pn_kid->pn_pos));
+
+ RootedValue arg(cx);
+
+ return optExpression(pn->pn_kid, &arg) &&
+ builder.returnStatement(arg, &pn->pn_pos, dst);
+ }
+
+ case PNK_DEBUGGER:
+ return builder.debuggerStatement(&pn->pn_pos, dst);
+
+ case PNK_CLASS:
+ return classDefinition(pn, false, dst);
+
+ case PNK_CLASSMETHODLIST:
+ {
+ NodeVector methods(cx);
+ if (!methods.reserve(pn->pn_count))
+ return false;
+
+ for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
+ MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+
+ RootedValue prop(cx);
+ if (!classMethod(next, &prop))
+ return false;
+ methods.infallibleAppend(prop);
+ }
+
+ return builder.classMethods(methods, dst);
+ }
+
+ case PNK_NOP:
+ return builder.emptyStatement(&pn->pn_pos, dst);
+
+ default:
+ LOCAL_NOT_REACHED("unexpected statement type");
+ }
+}
+
+bool
+ASTSerializer::classMethod(ParseNode* pn, MutableHandleValue dst)
+{
+ PropKind kind;
+ switch (pn->getOp()) {
+ case JSOP_INITPROP:
+ kind = PROP_INIT;
+ break;
+
+ case JSOP_INITPROP_GETTER:
+ kind = PROP_GETTER;
+ break;
+
+ case JSOP_INITPROP_SETTER:
+ kind = PROP_SETTER;
+ break;
+
+ default:
+ LOCAL_NOT_REACHED("unexpected object-literal property");
+ }
+
+ RootedValue key(cx), val(cx);
+ bool isStatic = pn->as<ClassMethod>().isStatic();
+ return propertyName(pn->pn_left, &key) &&
+ expression(pn->pn_right, &val) &&
+ builder.classMethod(key, val, kind, isStatic, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::leftAssociate(ParseNode* pn, MutableHandleValue dst)
+{
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+ MOZ_ASSERT(pn->pn_count >= 1);
+
+ ParseNodeKind kind = pn->getKind();
+ bool lor = kind == PNK_OR;
+ bool logop = lor || (kind == PNK_AND);
+
+ ParseNode* head = pn->pn_head;
+ RootedValue left(cx);
+ if (!expression(head, &left))
+ return false;
+ for (ParseNode* next = head->pn_next; next; next = next->pn_next) {
+ RootedValue right(cx);
+ if (!expression(next, &right))
+ return false;
+
+ TokenPos subpos(pn->pn_pos.begin, next->pn_pos.end);
+
+ if (logop) {
+ if (!builder.logicalExpression(lor, left, right, &subpos, &left))
+ return false;
+ } else {
+ BinaryOperator op = binop(pn->getKind(), pn->getOp());
+ LOCAL_ASSERT(op > BINOP_ERR && op < BINOP_LIMIT);
+
+ if (!builder.binaryExpression(op, left, right, &subpos, &left))
+ return false;
+ }
+ }
+
+ dst.set(left);
+ return true;
+}
+
+bool
+ASTSerializer::rightAssociate(ParseNode* pn, MutableHandleValue dst)
+{
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+ MOZ_ASSERT(pn->pn_count >= 1);
+
+ // First, we need to reverse the list, so that we can traverse it in the right order.
+ // It's OK to destructively reverse the list, because there are no other consumers.
+
+ ParseNode* head = pn->pn_head;
+ ParseNode* prev = nullptr;
+ ParseNode* current = head;
+ ParseNode* next;
+ while (current != nullptr) {
+ next = current->pn_next;
+ current->pn_next = prev;
+ prev = current;
+ current = next;
+ }
+
+ head = prev;
+
+ RootedValue right(cx);
+ if (!expression(head, &right))
+ return false;
+ for (ParseNode* next = head->pn_next; next; next = next->pn_next) {
+ RootedValue left(cx);
+ if (!expression(next, &left))
+ return false;
+
+ TokenPos subpos(pn->pn_pos.begin, next->pn_pos.end);
+
+ BinaryOperator op = binop(pn->getKind(), pn->getOp());
+ LOCAL_ASSERT(op > BINOP_ERR && op < BINOP_LIMIT);
+
+ if (!builder.binaryExpression(op, left, right, &subpos, &right))
+ return false;
+ }
+
+ dst.set(right);
+ return true;
+}
+
+bool
+ASTSerializer::comprehensionBlock(ParseNode* pn, MutableHandleValue dst)
+{
+ LOCAL_ASSERT(pn->isArity(PN_BINARY));
+
+ ParseNode* in = pn->pn_left;
+
+ LOCAL_ASSERT(in && (in->isKind(PNK_FORIN) || in->isKind(PNK_FOROF)));
+
+ bool isForEach = in->isKind(PNK_FORIN) && (pn->pn_iflags & JSITER_FOREACH);
+ bool isForOf = in->isKind(PNK_FOROF);
+
+ ParseNode* decl = in->pn_kid1;
+ if (decl->isKind(PNK_LEXICALSCOPE))
+ decl = decl->pn_expr;
+ MOZ_ASSERT(decl->pn_count == 1);
+
+ RootedValue patt(cx), src(cx);
+ return pattern(decl->pn_head, &patt) &&
+ expression(in->pn_kid3, &src) &&
+ builder.comprehensionBlock(patt, src, isForEach, isForOf, &in->pn_pos, dst);
+}
+
+bool
+ASTSerializer::comprehensionIf(ParseNode* pn, MutableHandleValue dst)
+{
+ LOCAL_ASSERT(pn->isKind(PNK_IF));
+ LOCAL_ASSERT(!pn->pn_kid3);
+
+ RootedValue patt(cx);
+ return pattern(pn->pn_kid1, &patt) &&
+ builder.comprehensionIf(patt, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::comprehension(ParseNode* pn, MutableHandleValue dst)
+{
+ // There are two array comprehension flavors.
+ // 1. The kind that was in ES4 for a while: [z for (x in y)]
+ // 2. The kind that was in ES6 for a while: [for (x of y) z]
+ // They have slightly different parse trees and scoping.
+ bool isLegacy = pn->isKind(PNK_LEXICALSCOPE);
+ ParseNode* next = isLegacy ? pn->pn_expr : pn;
+ LOCAL_ASSERT(next->isKind(PNK_COMPREHENSIONFOR));
+
+ NodeVector blocks(cx);
+ RootedValue filter(cx, MagicValue(JS_SERIALIZE_NO_NODE));
+ while (true) {
+ if (next->isKind(PNK_COMPREHENSIONFOR)) {
+ RootedValue block(cx);
+ if (!comprehensionBlock(next, &block) || !blocks.append(block))
+ return false;
+ next = next->pn_right;
+ } else if (next->isKind(PNK_IF)) {
+ if (isLegacy) {
+ MOZ_ASSERT(filter.isMagic(JS_SERIALIZE_NO_NODE));
+ if (!optExpression(next->pn_kid1, &filter))
+ return false;
+ } else {
+ // ES7 comprehension can contain multiple ComprehensionIfs.
+ RootedValue compif(cx);
+ if (!comprehensionIf(next, &compif) || !blocks.append(compif))
+ return false;
+ }
+ next = next->pn_kid2;
+ } else {
+ break;
+ }
+ }
+
+ LOCAL_ASSERT(next->isKind(PNK_ARRAYPUSH));
+
+ RootedValue body(cx);
+
+ return expression(next->pn_kid, &body) &&
+ builder.comprehensionExpression(body, blocks, filter, isLegacy, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::generatorExpression(ParseNode* pn, MutableHandleValue dst)
+{
+ // Just as there are two kinds of array comprehension (see
+ // ASTSerializer::comprehension), there are legacy and modern generator
+ // expression.
+ bool isLegacy = pn->isKind(PNK_LEXICALSCOPE);
+ ParseNode* next = isLegacy ? pn->pn_expr : pn;
+ LOCAL_ASSERT(next->isKind(PNK_COMPREHENSIONFOR));
+
+ NodeVector blocks(cx);
+ RootedValue filter(cx, MagicValue(JS_SERIALIZE_NO_NODE));
+ while (true) {
+ if (next->isKind(PNK_COMPREHENSIONFOR)) {
+ RootedValue block(cx);
+ if (!comprehensionBlock(next, &block) || !blocks.append(block))
+ return false;
+ next = next->pn_right;
+ } else if (next->isKind(PNK_IF)) {
+ if (isLegacy) {
+ MOZ_ASSERT(filter.isMagic(JS_SERIALIZE_NO_NODE));
+ if (!optExpression(next->pn_kid1, &filter))
+ return false;
+ } else {
+ // ES7 comprehension can contain multiple ComprehensionIfs.
+ RootedValue compif(cx);
+ if (!comprehensionIf(next, &compif) || !blocks.append(compif))
+ return false;
+ }
+ next = next->pn_kid2;
+ } else {
+ break;
+ }
+ }
+
+ LOCAL_ASSERT(next->isKind(PNK_SEMI) &&
+ next->pn_kid->isKind(PNK_YIELD) &&
+ next->pn_kid->pn_left);
+
+ RootedValue body(cx);
+
+ return expression(next->pn_kid->pn_left, &body) &&
+ builder.generatorExpression(body, blocks, filter, isLegacy, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
+{
+ JS_CHECK_RECURSION(cx, return false);
+ switch (pn->getKind()) {
+ case PNK_FUNCTION:
+ {
+ ASTType type = pn->pn_funbox->function()->isArrow() ? AST_ARROW_EXPR : AST_FUNC_EXPR;
+ return function(pn, type, dst);
+ }
+
+ case PNK_COMMA:
+ {
+ NodeVector exprs(cx);
+ return expressions(pn, exprs) &&
+ builder.sequenceExpression(exprs, &pn->pn_pos, dst);
+ }
+
+ case PNK_CONDITIONAL:
+ {
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid1->pn_pos));
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid2->pn_pos));
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid3->pn_pos));
+
+ RootedValue test(cx), cons(cx), alt(cx);
+
+ return expression(pn->pn_kid1, &test) &&
+ expression(pn->pn_kid2, &cons) &&
+ expression(pn->pn_kid3, &alt) &&
+ builder.conditionalExpression(test, cons, alt, &pn->pn_pos, dst);
+ }
+
+ case PNK_OR:
+ case PNK_AND:
+ return leftAssociate(pn, dst);
+
+ case PNK_PREINCREMENT:
+ case PNK_PREDECREMENT:
+ {
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid->pn_pos));
+
+ bool inc = pn->isKind(PNK_PREINCREMENT);
+ RootedValue expr(cx);
+ return expression(pn->pn_kid, &expr) &&
+ builder.updateExpression(expr, inc, true, &pn->pn_pos, dst);
+ }
+
+ case PNK_POSTINCREMENT:
+ case PNK_POSTDECREMENT:
+ {
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid->pn_pos));
+
+ bool inc = pn->isKind(PNK_POSTINCREMENT);
+ RootedValue expr(cx);
+ return expression(pn->pn_kid, &expr) &&
+ builder.updateExpression(expr, inc, false, &pn->pn_pos, dst);
+ }
+
+ case PNK_ASSIGN:
+ case PNK_ADDASSIGN:
+ case PNK_SUBASSIGN:
+ case PNK_BITORASSIGN:
+ case PNK_BITXORASSIGN:
+ case PNK_BITANDASSIGN:
+ case PNK_LSHASSIGN:
+ case PNK_RSHASSIGN:
+ case PNK_URSHASSIGN:
+ case PNK_MULASSIGN:
+ case PNK_DIVASSIGN:
+ case PNK_MODASSIGN:
+ case PNK_POWASSIGN:
+ {
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
+
+ AssignmentOperator op = aop(pn->getOp());
+ LOCAL_ASSERT(op > AOP_ERR && op < AOP_LIMIT);
+
+ RootedValue lhs(cx), rhs(cx);
+ return pattern(pn->pn_left, &lhs) &&
+ expression(pn->pn_right, &rhs) &&
+ builder.assignmentExpression(op, lhs, rhs, &pn->pn_pos, dst);
+ }
+
+ case PNK_ADD:
+ case PNK_SUB:
+ case PNK_STRICTEQ:
+ case PNK_EQ:
+ case PNK_STRICTNE:
+ case PNK_NE:
+ case PNK_LT:
+ case PNK_LE:
+ case PNK_GT:
+ case PNK_GE:
+ case PNK_LSH:
+ case PNK_RSH:
+ case PNK_URSH:
+ case PNK_STAR:
+ case PNK_DIV:
+ case PNK_MOD:
+ case PNK_BITOR:
+ case PNK_BITXOR:
+ case PNK_BITAND:
+ case PNK_IN:
+ case PNK_INSTANCEOF:
+ return leftAssociate(pn, dst);
+
+ case PNK_POW:
+ return rightAssociate(pn, dst);
+
+ case PNK_DELETENAME:
+ case PNK_DELETEPROP:
+ case PNK_DELETEELEM:
+ case PNK_DELETEEXPR:
+ case PNK_TYPEOFNAME:
+ case PNK_TYPEOFEXPR:
+ case PNK_VOID:
+ case PNK_NOT:
+ case PNK_BITNOT:
+ case PNK_POS:
+ case PNK_AWAIT:
+ case PNK_NEG: {
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid->pn_pos));
+
+ UnaryOperator op = unop(pn->getKind(), pn->getOp());
+ LOCAL_ASSERT(op > UNOP_ERR && op < UNOP_LIMIT);
+
+ RootedValue expr(cx);
+ return expression(pn->pn_kid, &expr) &&
+ builder.unaryExpression(op, expr, &pn->pn_pos, dst);
+ }
+
+ case PNK_GENEXP:
+ return generatorExpression(pn->generatorExpr(), dst);
+
+ case PNK_NEW:
+ case PNK_TAGGED_TEMPLATE:
+ case PNK_CALL:
+ case PNK_SUPERCALL:
+ {
+ ParseNode* next = pn->pn_head;
+ MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+
+ RootedValue callee(cx);
+ if (pn->isKind(PNK_SUPERCALL)) {
+ MOZ_ASSERT(next->isKind(PNK_SUPERBASE));
+ if (!builder.super(&next->pn_pos, &callee))
+ return false;
+ } else {
+ if (!expression(next, &callee))
+ return false;
+ }
+
+ NodeVector args(cx);
+ if (!args.reserve(pn->pn_count - 1))
+ return false;
+
+ for (next = next->pn_next; next; next = next->pn_next) {
+ MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+
+ RootedValue arg(cx);
+ if (!expression(next, &arg))
+ return false;
+ args.infallibleAppend(arg);
+ }
+
+ if (pn->getKind() == PNK_TAGGED_TEMPLATE)
+ return builder.taggedTemplate(callee, args, &pn->pn_pos, dst);
+
+ // SUPERCALL is Call(super, args)
+ return pn->isKind(PNK_NEW)
+ ? builder.newExpression(callee, args, &pn->pn_pos, dst)
+
+ : builder.callExpression(callee, args, &pn->pn_pos, dst);
+ }
+
+ case PNK_DOT:
+ {
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_expr->pn_pos));
+
+ RootedValue expr(cx);
+ RootedValue propname(cx);
+ RootedAtom pnAtom(cx, pn->pn_atom);
+
+ if (pn->as<PropertyAccess>().isSuper()) {
+ if (!builder.super(&pn->pn_expr->pn_pos, &expr))
+ return false;
+ } else {
+ if (!expression(pn->pn_expr, &expr))
+ return false;
+ }
+
+ return identifier(pnAtom, nullptr, &propname) &&
+ builder.memberExpression(false, expr, propname, &pn->pn_pos, dst);
+ }
+
+ case PNK_ELEM:
+ {
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
+
+ RootedValue left(cx), right(cx);
+
+ if (pn->as<PropertyByValue>().isSuper()) {
+ if (!builder.super(&pn->pn_left->pn_pos, &left))
+ return false;
+ } else {
+ if (!expression(pn->pn_left, &left))
+ return false;
+ }
+
+ return expression(pn->pn_right, &right) &&
+ builder.memberExpression(true, left, right, &pn->pn_pos, dst);
+ }
+
+ case PNK_CALLSITEOBJ:
+ {
+ NodeVector raw(cx);
+ if (!raw.reserve(pn->pn_head->pn_count))
+ return false;
+ for (ParseNode* next = pn->pn_head->pn_head; next; next = next->pn_next) {
+ MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+
+ RootedValue expr(cx);
+ expr.setString(next->pn_atom);
+ raw.infallibleAppend(expr);
+ }
+
+ NodeVector cooked(cx);
+ if (!cooked.reserve(pn->pn_count - 1))
+ return false;
+
+ for (ParseNode* next = pn->pn_head->pn_next; next; next = next->pn_next) {
+ MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+
+ RootedValue expr(cx);
+ expr.setString(next->pn_atom);
+ cooked.infallibleAppend(expr);
+ }
+
+ return builder.callSiteObj(raw, cooked, &pn->pn_pos, dst);
+ }
+
+ case PNK_ARRAY:
+ {
+ NodeVector elts(cx);
+ if (!elts.reserve(pn->pn_count))
+ return false;
+
+ for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
+ MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+
+ if (next->isKind(PNK_ELISION)) {
+ elts.infallibleAppend(NullValue());
+ } else {
+ RootedValue expr(cx);
+ if (!expression(next, &expr))
+ return false;
+ elts.infallibleAppend(expr);
+ }
+ }
+
+ return builder.arrayExpression(elts, &pn->pn_pos, dst);
+ }
+
+ case PNK_SPREAD:
+ {
+ RootedValue expr(cx);
+ return expression(pn->pn_kid, &expr) &&
+ builder.spreadExpression(expr, &pn->pn_pos, dst);
+ }
+
+ case PNK_COMPUTED_NAME:
+ {
+ RootedValue name(cx);
+ return expression(pn->pn_kid, &name) &&
+ builder.computedName(name, &pn->pn_pos, dst);
+ }
+
+ case PNK_OBJECT:
+ {
+ NodeVector elts(cx);
+ if (!elts.reserve(pn->pn_count))
+ return false;
+
+ for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
+ MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+
+ RootedValue prop(cx);
+ if (!property(next, &prop))
+ return false;
+ elts.infallibleAppend(prop);
+ }
+
+ return builder.objectExpression(elts, &pn->pn_pos, dst);
+ }
+
+ case PNK_NAME:
+ return identifier(pn, dst);
+
+ case PNK_THIS:
+ return builder.thisExpression(&pn->pn_pos, dst);
+
+ case PNK_TEMPLATE_STRING_LIST:
+ {
+ NodeVector elts(cx);
+ if (!elts.reserve(pn->pn_count))
+ return false;
+
+ for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
+ MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+
+ RootedValue expr(cx);
+ if (!expression(next, &expr))
+ return false;
+ elts.infallibleAppend(expr);
+ }
+
+ return builder.templateLiteral(elts, &pn->pn_pos, dst);
+ }
+
+ case PNK_TEMPLATE_STRING:
+ case PNK_STRING:
+ case PNK_REGEXP:
+ case PNK_NUMBER:
+ case PNK_TRUE:
+ case PNK_FALSE:
+ case PNK_NULL:
+ return literal(pn, dst);
+
+ case PNK_YIELD_STAR:
+ {
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
+
+ RootedValue arg(cx);
+ return expression(pn->pn_left, &arg) &&
+ builder.yieldExpression(arg, Delegating, &pn->pn_pos, dst);
+ }
+
+ case PNK_YIELD:
+ {
+ MOZ_ASSERT_IF(pn->pn_left, pn->pn_pos.encloses(pn->pn_left->pn_pos));
+
+ RootedValue arg(cx);
+ return optExpression(pn->pn_left, &arg) &&
+ builder.yieldExpression(arg, NotDelegating, &pn->pn_pos, dst);
+ }
+
+ case PNK_ARRAYCOMP:
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_head->pn_pos));
+
+ /* NB: it's no longer the case that pn_count could be 2. */
+ LOCAL_ASSERT(pn->pn_count == 1);
+ return comprehension(pn->pn_head, dst);
+
+ case PNK_CLASS:
+ return classDefinition(pn, true, dst);
+
+ case PNK_NEWTARGET:
+ {
+ MOZ_ASSERT(pn->pn_left->isKind(PNK_POSHOLDER));
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
+ MOZ_ASSERT(pn->pn_right->isKind(PNK_POSHOLDER));
+ MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
+
+ RootedValue newIdent(cx);
+ RootedValue targetIdent(cx);
+
+ RootedAtom newStr(cx, cx->names().new_);
+ RootedAtom targetStr(cx, cx->names().target);
+
+ return identifier(newStr, &pn->pn_left->pn_pos, &newIdent) &&
+ identifier(targetStr, &pn->pn_right->pn_pos, &targetIdent) &&
+ builder.metaProperty(newIdent, targetIdent, &pn->pn_pos, dst);
+ }
+
+ case PNK_SETTHIS:
+ // SETTHIS is used to assign the result of a super() call to |this|.
+ // It's not part of the original AST, so just forward to the call.
+ MOZ_ASSERT(pn->pn_left->isKind(PNK_NAME));
+ return expression(pn->pn_right, dst);
+
+ default:
+ LOCAL_NOT_REACHED("unexpected expression type");
+ }
+}
+
+bool
+ASTSerializer::propertyName(ParseNode* pn, MutableHandleValue dst)
+{
+ if (pn->isKind(PNK_COMPUTED_NAME))
+ return expression(pn, dst);
+ if (pn->isKind(PNK_OBJECT_PROPERTY_NAME))
+ return identifier(pn, dst);
+
+ LOCAL_ASSERT(pn->isKind(PNK_STRING) || pn->isKind(PNK_NUMBER));
+
+ return literal(pn, dst);
+}
+
+bool
+ASTSerializer::property(ParseNode* pn, MutableHandleValue dst)
+{
+ if (pn->isKind(PNK_MUTATEPROTO)) {
+ RootedValue val(cx);
+ return expression(pn->pn_kid, &val) &&
+ builder.prototypeMutation(val, &pn->pn_pos, dst);
+ }
+
+ PropKind kind;
+ switch (pn->getOp()) {
+ case JSOP_INITPROP:
+ kind = PROP_INIT;
+ break;
+
+ case JSOP_INITPROP_GETTER:
+ kind = PROP_GETTER;
+ break;
+
+ case JSOP_INITPROP_SETTER:
+ kind = PROP_SETTER;
+ break;
+
+ default:
+ LOCAL_NOT_REACHED("unexpected object-literal property");
+ }
+
+ bool isShorthand = pn->isKind(PNK_SHORTHAND);
+ bool isMethod =
+ pn->pn_right->isKind(PNK_FUNCTION) &&
+ pn->pn_right->pn_funbox->function()->kind() == JSFunction::Method;
+ RootedValue key(cx), val(cx);
+ return propertyName(pn->pn_left, &key) &&
+ expression(pn->pn_right, &val) &&
+ builder.propertyInitializer(key, val, kind, isShorthand, isMethod, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst)
+{
+ RootedValue val(cx);
+ switch (pn->getKind()) {
+ case PNK_TEMPLATE_STRING:
+ case PNK_STRING:
+ val.setString(pn->pn_atom);
+ break;
+
+ case PNK_REGEXP:
+ {
+ RootedObject re1(cx, pn->as<RegExpLiteral>().objbox()->object);
+ LOCAL_ASSERT(re1 && re1->is<RegExpObject>());
+
+ RootedObject re2(cx, CloneRegExpObject(cx, re1));
+ if (!re2)
+ return false;
+
+ val.setObject(*re2);
+ break;
+ }
+
+ case PNK_NUMBER:
+ val.setNumber(pn->pn_dval);
+ break;
+
+ case PNK_NULL:
+ val.setNull();
+ break;
+
+ case PNK_TRUE:
+ val.setBoolean(true);
+ break;
+
+ case PNK_FALSE:
+ val.setBoolean(false);
+ break;
+
+ default:
+ LOCAL_NOT_REACHED("unexpected literal type");
+ }
+
+ return builder.literal(val, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::arrayPattern(ParseNode* pn, MutableHandleValue dst)
+{
+ MOZ_ASSERT(pn->isKind(PNK_ARRAY));
+
+ NodeVector elts(cx);
+ if (!elts.reserve(pn->pn_count))
+ return false;
+
+ for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
+ if (next->isKind(PNK_ELISION)) {
+ elts.infallibleAppend(NullValue());
+ } else if (next->isKind(PNK_SPREAD)) {
+ RootedValue target(cx);
+ RootedValue spread(cx);
+ if (!pattern(next->pn_kid, &target))
+ return false;
+ if(!builder.spreadExpression(target, &next->pn_pos, &spread))
+ return false;
+ elts.infallibleAppend(spread);
+ } else {
+ RootedValue patt(cx);
+ if (!pattern(next, &patt))
+ return false;
+ elts.infallibleAppend(patt);
+ }
+ }
+
+ return builder.arrayPattern(elts, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::objectPattern(ParseNode* pn, MutableHandleValue dst)
+{
+ MOZ_ASSERT(pn->isKind(PNK_OBJECT));
+
+ NodeVector elts(cx);
+ if (!elts.reserve(pn->pn_count))
+ return false;
+
+ for (ParseNode* propdef = pn->pn_head; propdef; propdef = propdef->pn_next) {
+ LOCAL_ASSERT(propdef->isKind(PNK_MUTATEPROTO) != propdef->isOp(JSOP_INITPROP));
+
+ RootedValue key(cx);
+ ParseNode* target;
+ if (propdef->isKind(PNK_MUTATEPROTO)) {
+ RootedValue pname(cx, StringValue(cx->names().proto));
+ if (!builder.literal(pname, &propdef->pn_pos, &key))
+ return false;
+ target = propdef->pn_kid;
+ } else {
+ if (!propertyName(propdef->pn_left, &key))
+ return false;
+ target = propdef->pn_right;
+ }
+
+ RootedValue patt(cx), prop(cx);
+ if (!pattern(target, &patt) ||
+ !builder.propertyPattern(key, patt, propdef->isKind(PNK_SHORTHAND), &propdef->pn_pos,
+ &prop))
+ {
+ return false;
+ }
+
+ elts.infallibleAppend(prop);
+ }
+
+ return builder.objectPattern(elts, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::pattern(ParseNode* pn, MutableHandleValue dst)
+{
+ JS_CHECK_RECURSION(cx, return false);
+ switch (pn->getKind()) {
+ case PNK_OBJECT:
+ return objectPattern(pn, dst);
+
+ case PNK_ARRAY:
+ return arrayPattern(pn, dst);
+
+ default:
+ return expression(pn, dst);
+ }
+}
+
+bool
+ASTSerializer::identifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst)
+{
+ RootedValue atomContentsVal(cx, unrootedAtomContents(atom));
+ return builder.identifier(atomContentsVal, pos, dst);
+}
+
+bool
+ASTSerializer::identifier(ParseNode* pn, MutableHandleValue dst)
+{
+ LOCAL_ASSERT(pn->isArity(PN_NAME) || pn->isArity(PN_NULLARY));
+ LOCAL_ASSERT(pn->pn_atom);
+
+ RootedAtom pnAtom(cx, pn->pn_atom);
+ return identifier(pnAtom, &pn->pn_pos, dst);
+}
+
+bool
+ASTSerializer::function(ParseNode* pn, ASTType type, MutableHandleValue dst)
+{
+ RootedFunction func(cx, pn->pn_funbox->function());
+
+ GeneratorStyle generatorStyle =
+ pn->pn_funbox->isGenerator()
+ ? (pn->pn_funbox->isLegacyGenerator()
+ ? GeneratorStyle::Legacy
+ : GeneratorStyle::ES6)
+ : GeneratorStyle::None;
+
+ bool isAsync = pn->pn_funbox->isAsync();
+ bool isExpression =
+#if JS_HAS_EXPR_CLOSURES
+ func->isExprBody();
+#else
+ false;
+#endif
+
+ RootedValue id(cx);
+ RootedAtom funcAtom(cx, func->name());
+ if (!optIdentifier(funcAtom, nullptr, &id))
+ return false;
+
+ NodeVector args(cx);
+ NodeVector defaults(cx);
+
+ RootedValue body(cx), rest(cx);
+ if (func->hasRest())
+ rest.setUndefined();
+ else
+ rest.setNull();
+ return functionArgsAndBody(pn->pn_body, args, defaults, isAsync, isExpression, &body, &rest) &&
+ builder.function(type, &pn->pn_pos, id, args, defaults, body,
+ rest, generatorStyle, isAsync, isExpression, dst);
+}
+
+bool
+ASTSerializer::functionArgsAndBody(ParseNode* pn, NodeVector& args, NodeVector& defaults,
+ bool isAsync, bool isExpression,
+ MutableHandleValue body, MutableHandleValue rest)
+{
+ ParseNode* pnargs;
+ ParseNode* pnbody;
+
+ /* Extract the args and body separately. */
+ if (pn->isKind(PNK_PARAMSBODY)) {
+ pnargs = pn;
+ pnbody = pn->last();
+ } else {
+ pnargs = nullptr;
+ pnbody = pn;
+ }
+
+ if (pnbody->isKind(PNK_LEXICALSCOPE))
+ pnbody = pnbody->scopeBody();
+
+ /* Serialize the arguments and body. */
+ switch (pnbody->getKind()) {
+ case PNK_RETURN: /* expression closure, no destructured args */
+ return functionArgs(pn, pnargs, args, defaults, rest) &&
+ expression(pnbody->pn_kid, body);
+
+ case PNK_STATEMENTLIST: /* statement closure */
+ {
+ ParseNode* pnstart = pnbody->pn_head;
+
+ // Skip over initial yield in generator.
+ if (pnstart && pnstart->isKind(PNK_YIELD)) {
+ MOZ_ASSERT(pnstart->getOp() == JSOP_INITIALYIELD);
+ pnstart = pnstart->pn_next;
+ }
+
+ // Async arrow with expression body is converted into STATEMENTLIST
+ // to insert initial yield.
+ if (isAsync && isExpression) {
+ MOZ_ASSERT(pnstart->getKind() == PNK_RETURN);
+ return functionArgs(pn, pnargs, args, defaults, rest) &&
+ expression(pnstart->pn_kid, body);
+ }
+
+ return functionArgs(pn, pnargs, args, defaults, rest) &&
+ functionBody(pnstart, &pnbody->pn_pos, body);
+ }
+
+ default:
+ LOCAL_NOT_REACHED("unexpected function contents");
+ }
+}
+
+bool
+ASTSerializer::functionArgs(ParseNode* pn, ParseNode* pnargs,
+ NodeVector& args, NodeVector& defaults,
+ MutableHandleValue rest)
+{
+ if (!pnargs)
+ return true;
+
+ RootedValue node(cx);
+ bool defaultsNull = true;
+ MOZ_ASSERT(defaults.empty(),
+ "must be initially empty for it to be proper to clear this "
+ "when there are no defaults");
+
+ for (ParseNode* arg = pnargs->pn_head; arg && arg != pnargs->last(); arg = arg->pn_next) {
+ ParseNode* pat;
+ ParseNode* defNode;
+ if (arg->isKind(PNK_NAME) || arg->isKind(PNK_ARRAY) || arg->isKind(PNK_OBJECT)) {
+ pat = arg;
+ defNode = nullptr;
+ } else {
+ MOZ_ASSERT(arg->isKind(PNK_ASSIGN));
+ pat = arg->pn_left;
+ defNode = arg->pn_right;
+ }
+
+ // Process the name or pattern.
+ MOZ_ASSERT(pat->isKind(PNK_NAME) || pat->isKind(PNK_ARRAY) || pat->isKind(PNK_OBJECT));
+ if (!pattern(pat, &node))
+ return false;
+ if (rest.isUndefined() && arg->pn_next == pnargs->last()) {
+ rest.setObject(node.toObject());
+ } else {
+ if (!args.append(node))
+ return false;
+ }
+
+ // Process its default (or lack thereof).
+ if (defNode) {
+ defaultsNull = false;
+ RootedValue def(cx);
+ if (!expression(defNode, &def) || !defaults.append(def))
+ return false;
+ } else {
+ if (!defaults.append(NullValue()))
+ return false;
+ }
+ }
+ MOZ_ASSERT(!rest.isUndefined(),
+ "if a rest argument was present (signified by "
+ "|rest.isUndefined()| initially), the rest node was properly "
+ "recorded");
+
+ if (defaultsNull)
+ defaults.clear();
+
+ return true;
+}
+
+bool
+ASTSerializer::functionBody(ParseNode* pn, TokenPos* pos, MutableHandleValue dst)
+{
+ NodeVector elts(cx);
+
+ /* We aren't sure how many elements there are up front, so we'll check each append. */
+ for (ParseNode* next = pn; next; next = next->pn_next) {
+ RootedValue child(cx);
+ if (!sourceElement(next, &child) || !elts.append(child))
+ return false;
+ }
+
+ return builder.blockStatement(elts, pos, dst);
+}
+
+static bool
+reflect_parse(JSContext* cx, uint32_t argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 1) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+ "Reflect.parse", "0", "s");
+ return false;
+ }
+
+ RootedString src(cx, ToString<CanGC>(cx, args[0]));
+ if (!src)
+ return false;
+
+ ScopedJSFreePtr<char> filename;
+ uint32_t lineno = 1;
+ bool loc = true;
+ RootedObject builder(cx);
+ ParseTarget target = ParseTarget::Script;
+
+ RootedValue arg(cx, args.get(1));
+
+ if (!arg.isNullOrUndefined()) {
+ if (!arg.isObject()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, arg, nullptr,
+ "not an object", nullptr);
+ return false;
+ }
+
+ RootedObject config(cx, &arg.toObject());
+
+ RootedValue prop(cx);
+
+ /* config.loc */
+ RootedId locId(cx, NameToId(cx->names().loc));
+ RootedValue trueVal(cx, BooleanValue(true));
+ if (!GetPropertyDefault(cx, config, locId, trueVal, &prop))
+ return false;
+
+ loc = ToBoolean(prop);
+
+ if (loc) {
+ /* config.source */
+ RootedId sourceId(cx, NameToId(cx->names().source));
+ RootedValue nullVal(cx, NullValue());
+ if (!GetPropertyDefault(cx, config, sourceId, nullVal, &prop))
+ return false;
+
+ if (!prop.isNullOrUndefined()) {
+ RootedString str(cx, ToString<CanGC>(cx, prop));
+ if (!str)
+ return false;
+
+ filename = JS_EncodeString(cx, str);
+ if (!filename)
+ return false;
+ }
+
+ /* config.line */
+ RootedId lineId(cx, NameToId(cx->names().line));
+ RootedValue oneValue(cx, Int32Value(1));
+ if (!GetPropertyDefault(cx, config, lineId, oneValue, &prop) ||
+ !ToUint32(cx, prop, &lineno)) {
+ return false;
+ }
+ }
+
+ /* config.builder */
+ RootedId builderId(cx, NameToId(cx->names().builder));
+ RootedValue nullVal(cx, NullValue());
+ if (!GetPropertyDefault(cx, config, builderId, nullVal, &prop))
+ return false;
+
+ if (!prop.isNullOrUndefined()) {
+ if (!prop.isObject()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, prop, nullptr,
+ "not an object", nullptr);
+ return false;
+ }
+ builder = &prop.toObject();
+ }
+
+ /* config.target */
+ RootedId targetId(cx, NameToId(cx->names().target));
+ RootedValue scriptVal(cx, StringValue(cx->names().script));
+ if (!GetPropertyDefault(cx, config, targetId, scriptVal, &prop))
+ return false;
+
+ if (!prop.isString()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK,
+ prop, nullptr, "not 'script' or 'module'", nullptr);
+ return false;
+ }
+
+ RootedString stringProp(cx, prop.toString());
+ bool isScript = false;
+ bool isModule = false;
+ if (!EqualStrings(cx, stringProp, cx->names().script, &isScript))
+ return false;
+
+ if (!EqualStrings(cx, stringProp, cx->names().module, &isModule))
+ return false;
+
+ if (isScript) {
+ target = ParseTarget::Script;
+ } else if (isModule) {
+ target = ParseTarget::Module;
+ } else {
+ JS_ReportErrorASCII(cx, "Bad target value, expected 'script' or 'module'");
+ return false;
+ }
+ }
+
+ /* Extract the builder methods first to report errors before parsing. */
+ ASTSerializer serialize(cx, loc, filename, lineno);
+ if (!serialize.init(builder))
+ return false;
+
+ JSLinearString* linear = src->ensureLinear(cx);
+ if (!linear)
+ return false;
+
+ AutoStableStringChars linearChars(cx);
+ if (!linearChars.initTwoByte(cx, linear))
+ return false;
+
+ CompileOptions options(cx);
+ options.setFileAndLine(filename, lineno);
+ options.setCanLazilyParse(false);
+ mozilla::Range<const char16_t> chars = linearChars.twoByteRange();
+ UsedNameTracker usedNames(cx);
+ if (!usedNames.init())
+ return false;
+ Parser<FullParseHandler> parser(cx, cx->tempLifoAlloc(), options, chars.begin().get(),
+ chars.length(), /* foldConstants = */ false, usedNames,
+ nullptr, nullptr);
+ if (!parser.checkOptions())
+ return false;
+
+ serialize.setParser(&parser);
+
+ ParseNode* pn;
+ if (target == ParseTarget::Script) {
+ pn = parser.parse();
+ if (!pn)
+ return false;
+ } else {
+ if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global()))
+ return false;
+
+ Rooted<ModuleObject*> module(cx, ModuleObject::create(cx));
+ if (!module)
+ return false;
+
+ ModuleBuilder builder(cx, module);
+ ModuleSharedContext modulesc(cx, module, &cx->global()->emptyGlobalScope(), builder);
+ pn = parser.moduleBody(&modulesc);
+ if (!pn)
+ return false;
+
+ MOZ_ASSERT(pn->getKind() == PNK_MODULE);
+ pn = pn->pn_body;
+ }
+
+ RootedValue val(cx);
+ if (!serialize.program(pn, &val)) {
+ args.rval().setNull();
+ return false;
+ }
+
+ args.rval().set(val);
+ return true;
+}
+
+JS_PUBLIC_API(bool)
+JS_InitReflectParse(JSContext* cx, HandleObject global)
+{
+ RootedValue reflectVal(cx);
+ if (!GetProperty(cx, global, global, cx->names().Reflect, &reflectVal))
+ return false;
+ if (!reflectVal.isObject()) {
+ JS_ReportErrorASCII(cx, "JS_InitReflectParse must be called during global initialization");
+ return false;
+ }
+
+ RootedObject reflectObj(cx, &reflectVal.toObject());
+ return JS_DefineFunction(cx, reflectObj, "parse", reflect_parse, 1, 0);
+}
diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp
new file mode 100644
index 000000000..80a4bb5bd
--- /dev/null
+++ b/js/src/builtin/RegExp.cpp
@@ -0,0 +1,1781 @@
+/* -*- 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 "builtin/RegExp.h"
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/TypeTraits.h"
+
+#include "jscntxt.h"
+
+#include "irregexp/RegExpParser.h"
+#include "jit/InlinableNatives.h"
+#include "vm/RegExpStatics.h"
+#include "vm/SelfHosting.h"
+#include "vm/StringBuffer.h"
+#include "vm/Unicode.h"
+
+#include "jsobjinlines.h"
+
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+using namespace js::unicode;
+
+using mozilla::ArrayLength;
+using mozilla::CheckedInt;
+using mozilla::Maybe;
+
+/*
+ * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
+ * steps 3, 16-25.
+ */
+bool
+js::CreateRegExpMatchResult(JSContext* cx, HandleString input, const MatchPairs& matches,
+ MutableHandleValue rval)
+{
+ MOZ_ASSERT(input);
+
+ /*
+ * Create the (slow) result array for a match.
+ *
+ * Array contents:
+ * 0: matched string
+ * 1..pairCount-1: paren matches
+ * input: input string
+ * index: start index for the match
+ */
+
+ /* Get the templateObject that defines the shape and type of the output object */
+ JSObject* templateObject = cx->compartment()->regExps.getOrCreateMatchResultTemplateObject(cx);
+ if (!templateObject)
+ return false;
+
+ size_t numPairs = matches.length();
+ MOZ_ASSERT(numPairs > 0);
+
+ /* Step 17. */
+ RootedArrayObject arr(cx, NewDenseFullyAllocatedArrayWithTemplate(cx, numPairs, templateObject));
+ if (!arr)
+ return false;
+
+ /* Steps 22-24.
+ * Store a Value for each pair. */
+ for (size_t i = 0; i < numPairs; i++) {
+ const MatchPair& pair = matches[i];
+
+ if (pair.isUndefined()) {
+ MOZ_ASSERT(i != 0); /* Since we had a match, first pair must be present. */
+ arr->setDenseInitializedLength(i + 1);
+ arr->initDenseElement(i, UndefinedValue());
+ } else {
+ JSLinearString* str = NewDependentString(cx, input, pair.start, pair.length());
+ if (!str)
+ return false;
+ arr->setDenseInitializedLength(i + 1);
+ arr->initDenseElement(i, StringValue(str));
+ }
+ }
+
+ /* Step 20 (reordered).
+ * Set the |index| property. (TemplateObject positions it in slot 0) */
+ arr->setSlot(0, Int32Value(matches[0].start));
+
+ /* Step 21 (reordered).
+ * Set the |input| property. (TemplateObject positions it in slot 1) */
+ arr->setSlot(1, StringValue(input));
+
+#ifdef DEBUG
+ RootedValue test(cx);
+ RootedId id(cx, NameToId(cx->names().index));
+ if (!NativeGetProperty(cx, arr, id, &test))
+ return false;
+ MOZ_ASSERT(test == arr->getSlot(0));
+ id = NameToId(cx->names().input);
+ if (!NativeGetProperty(cx, arr, id, &test))
+ return false;
+ MOZ_ASSERT(test == arr->getSlot(1));
+#endif
+
+ /* Step 25. */
+ rval.setObject(*arr);
+ return true;
+}
+
+static int32_t
+CreateRegExpSearchResult(JSContext* cx, const MatchPairs& matches)
+{
+ /* Fit the start and limit of match into a int32_t. */
+ uint32_t position = matches[0].start;
+ uint32_t lastIndex = matches[0].limit;
+ MOZ_ASSERT(position < 0x8000);
+ MOZ_ASSERT(lastIndex < 0x8000);
+ return position | (lastIndex << 15);
+}
+
+/*
+ * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
+ * steps 3, 9-14, except 12.a.i, 12.c.i.1.
+ */
+static RegExpRunStatus
+ExecuteRegExpImpl(JSContext* cx, RegExpStatics* res, RegExpShared& re, HandleLinearString input,
+ size_t searchIndex, MatchPairs* matches, size_t* endIndex)
+{
+ RegExpRunStatus status = re.execute(cx, input, searchIndex, matches, endIndex);
+
+ /* Out of spec: Update RegExpStatics. */
+ if (status == RegExpRunStatus_Success && res) {
+ if (matches) {
+ if (!res->updateFromMatchPairs(cx, input, *matches))
+ return RegExpRunStatus_Error;
+ } else {
+ res->updateLazily(cx, input, &re, searchIndex);
+ }
+ }
+ return status;
+}
+
+/* Legacy ExecuteRegExp behavior is baked into the JSAPI. */
+bool
+js::ExecuteRegExpLegacy(JSContext* cx, RegExpStatics* res, RegExpObject& reobj,
+ HandleLinearString input, size_t* lastIndex, bool test,
+ MutableHandleValue rval)
+{
+ RegExpGuard shared(cx);
+ if (!reobj.getShared(cx, &shared))
+ return false;
+
+ ScopedMatchPairs matches(&cx->tempLifoAlloc());
+
+ RegExpRunStatus status = ExecuteRegExpImpl(cx, res, *shared, input, *lastIndex,
+ &matches, nullptr);
+ if (status == RegExpRunStatus_Error)
+ return false;
+
+ if (status == RegExpRunStatus_Success_NotFound) {
+ /* ExecuteRegExp() previously returned an array or null. */
+ rval.setNull();
+ return true;
+ }
+
+ *lastIndex = matches[0].limit;
+
+ if (test) {
+ /* Forbid an array, as an optimization. */
+ rval.setBoolean(true);
+ return true;
+ }
+
+ return CreateRegExpMatchResult(cx, input, matches, rval);
+}
+
+static bool
+CheckPatternSyntax(JSContext* cx, HandleAtom pattern, RegExpFlag flags)
+{
+ CompileOptions options(cx);
+ frontend::TokenStream dummyTokenStream(cx, options, nullptr, 0, nullptr);
+ return irregexp::ParsePatternSyntax(dummyTokenStream, cx->tempLifoAlloc(), pattern,
+ flags & UnicodeFlag);
+}
+
+enum RegExpSharedUse {
+ UseRegExpShared,
+ DontUseRegExpShared
+};
+
+/*
+ * ES 2016 draft Mar 25, 2016 21.2.3.2.2.
+ *
+ * Steps 14-15 set |obj|'s "lastIndex" property to zero. Some of
+ * RegExpInitialize's callers have a fresh RegExp not yet exposed to script:
+ * in these cases zeroing "lastIndex" is infallible. But others have a RegExp
+ * whose "lastIndex" property might have been made non-writable: here, zeroing
+ * "lastIndex" can fail. We efficiently solve this problem by completely
+ * removing "lastIndex" zeroing from the provided function.
+ *
+ * CALLERS MUST HANDLE "lastIndex" ZEROING THEMSELVES!
+ *
+ * Because this function only ever returns a user-provided |obj| in the spec,
+ * we omit it and just return the usual success/failure.
+ */
+static bool
+RegExpInitializeIgnoringLastIndex(JSContext* cx, Handle<RegExpObject*> obj,
+ HandleValue patternValue, HandleValue flagsValue,
+ RegExpSharedUse sharedUse = DontUseRegExpShared)
+{
+ RootedAtom pattern(cx);
+ if (patternValue.isUndefined()) {
+ /* Step 1. */
+ pattern = cx->names().empty;
+ } else {
+ /* Step 2. */
+ pattern = ToAtom<CanGC>(cx, patternValue);
+ if (!pattern)
+ return false;
+ }
+
+ /* Step 3. */
+ RegExpFlag flags = RegExpFlag(0);
+ if (!flagsValue.isUndefined()) {
+ /* Step 4. */
+ RootedString flagStr(cx, ToString<CanGC>(cx, flagsValue));
+ if (!flagStr)
+ return false;
+
+ /* Step 5. */
+ if (!ParseRegExpFlags(cx, flagStr, &flags))
+ return false;
+ }
+
+ if (sharedUse == UseRegExpShared) {
+ /* Steps 7-8. */
+ RegExpGuard re(cx);
+ if (!cx->compartment()->regExps.get(cx, pattern, flags, &re))
+ return false;
+
+ /* Steps 9-12. */
+ obj->initIgnoringLastIndex(pattern, flags);
+
+ obj->setShared(*re);
+ } else {
+ /* Steps 7-8. */
+ if (!CheckPatternSyntax(cx, pattern, flags))
+ return false;
+
+ /* Steps 9-12. */
+ obj->initIgnoringLastIndex(pattern, flags);
+ }
+
+ return true;
+}
+
+/* ES 2016 draft Mar 25, 2016 21.2.3.2.3. */
+bool
+js::RegExpCreate(JSContext* cx, HandleValue patternValue, HandleValue flagsValue,
+ MutableHandleValue rval)
+{
+ /* Step 1. */
+ Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx));
+ if (!regexp)
+ return false;
+
+ /* Step 2. */
+ if (!RegExpInitializeIgnoringLastIndex(cx, regexp, patternValue, flagsValue, UseRegExpShared))
+ return false;
+ regexp->zeroLastIndex(cx);
+
+ rval.setObject(*regexp);
+ return true;
+}
+
+MOZ_ALWAYS_INLINE bool
+IsRegExpObject(HandleValue v)
+{
+ return v.isObject() && v.toObject().is<RegExpObject>();
+}
+
+/* ES6 draft rc3 7.2.8. */
+bool
+js::IsRegExp(JSContext* cx, HandleValue value, bool* result)
+{
+ /* Step 1. */
+ if (!value.isObject()) {
+ *result = false;
+ return true;
+ }
+ RootedObject obj(cx, &value.toObject());
+
+ /* Steps 2-3. */
+ RootedValue isRegExp(cx);
+ RootedId matchId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().match));
+ if (!GetProperty(cx, obj, obj, matchId, &isRegExp))
+ return false;
+
+ /* Step 4. */
+ if (!isRegExp.isUndefined()) {
+ *result = ToBoolean(isRegExp);
+ return true;
+ }
+
+ /* Steps 5-6. */
+ ESClass cls;
+ if (!GetClassOfValue(cx, value, &cls))
+ return false;
+
+ *result = cls == ESClass::RegExp;
+ return true;
+}
+
+/* ES6 B.2.5.1. */
+MOZ_ALWAYS_INLINE bool
+regexp_compile_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(IsRegExpObject(args.thisv()));
+
+ Rooted<RegExpObject*> regexp(cx, &args.thisv().toObject().as<RegExpObject>());
+
+ // Step 3.
+ RootedValue patternValue(cx, args.get(0));
+ ESClass cls;
+ if (!GetClassOfValue(cx, patternValue, &cls))
+ return false;
+ if (cls == ESClass::RegExp) {
+ // Step 3a.
+ if (args.hasDefined(1)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NEWREGEXP_FLAGGED);
+ return false;
+ }
+
+ // Beware! |patternObj| might be a proxy into another compartment, so
+ // don't assume |patternObj.is<RegExpObject>()|. For the same reason,
+ // don't reuse the RegExpShared below.
+ RootedObject patternObj(cx, &patternValue.toObject());
+
+ RootedAtom sourceAtom(cx);
+ RegExpFlag flags;
+ {
+ // Step 3b.
+ RegExpGuard g(cx);
+ if (!RegExpToShared(cx, patternObj, &g))
+ return false;
+
+ sourceAtom = g->getSource();
+ flags = g->getFlags();
+ }
+
+ // Step 5, minus lastIndex zeroing.
+ regexp->initIgnoringLastIndex(sourceAtom, flags);
+ } else {
+ // Step 4.
+ RootedValue P(cx, patternValue);
+ RootedValue F(cx, args.get(1));
+
+ // Step 5, minus lastIndex zeroing.
+ if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F))
+ return false;
+ }
+
+ // The final niggling bit of step 5.
+ //
+ // |regexp| is user-exposed, but if its "lastIndex" property hasn't been
+ // made non-writable, we can still use a fast path to zero it.
+ if (regexp->lookupPure(cx->names().lastIndex)->writable()) {
+ regexp->zeroLastIndex(cx);
+ } else {
+ RootedValue zero(cx, Int32Value(0));
+ if (!SetProperty(cx, regexp, cx->names().lastIndex, zero))
+ return false;
+ }
+
+ args.rval().setObject(*regexp);
+ return true;
+}
+
+static bool
+regexp_compile(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ /* Steps 1-2. */
+ return CallNonGenericMethod<IsRegExpObject, regexp_compile_impl>(cx, args);
+}
+
+/*
+ * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.3.1.
+ */
+bool
+js::regexp_construct(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Steps 1.
+ bool patternIsRegExp;
+ if (!IsRegExp(cx, args.get(0), &patternIsRegExp))
+ return false;
+
+ // We can delay step 3 and step 4a until later, during
+ // GetPrototypeFromCallableConstructor calls. Accessing the new.target
+ // and the callee from the stack is unobservable.
+ if (!args.isConstructing()) {
+ // Step 3.b.
+ if (patternIsRegExp && !args.hasDefined(1)) {
+ RootedObject patternObj(cx, &args[0].toObject());
+
+ // Step 3.b.i.
+ RootedValue patternConstructor(cx);
+ if (!GetProperty(cx, patternObj, patternObj, cx->names().constructor, &patternConstructor))
+ return false;
+
+ // Step 3.b.ii.
+ if (patternConstructor.isObject() && patternConstructor.toObject() == args.callee()) {
+ args.rval().set(args[0]);
+ return true;
+ }
+ }
+ }
+
+ RootedValue patternValue(cx, args.get(0));
+
+ // Step 4.
+ ESClass cls;
+ if (!GetClassOfValue(cx, patternValue, &cls))
+ return false;
+ if (cls == ESClass::RegExp) {
+ // Beware! |patternObj| might be a proxy into another compartment, so
+ // don't assume |patternObj.is<RegExpObject>()|. For the same reason,
+ // don't reuse the RegExpShared below.
+ RootedObject patternObj(cx, &patternValue.toObject());
+
+ RootedAtom sourceAtom(cx);
+ RegExpFlag flags;
+ {
+ // Step 4.a.
+ RegExpGuard g(cx);
+ if (!RegExpToShared(cx, patternObj, &g))
+ return false;
+ sourceAtom = g->getSource();
+
+ // Step 4.b.
+ // Get original flags in all cases, to compare with passed flags.
+ flags = g->getFlags();
+ }
+
+ // Step 7.
+ RootedObject proto(cx);
+ if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
+ return false;
+
+ Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, proto));
+ if (!regexp)
+ return false;
+
+ // Step 8.
+ if (args.hasDefined(1)) {
+ // Step 4.c / 21.2.3.2.2 RegExpInitialize step 4.
+ RegExpFlag flagsArg = RegExpFlag(0);
+ RootedString flagStr(cx, ToString<CanGC>(cx, args[1]));
+ if (!flagStr)
+ return false;
+ if (!ParseRegExpFlags(cx, flagStr, &flagsArg))
+ return false;
+
+ if (!(flags & UnicodeFlag) && flagsArg & UnicodeFlag) {
+ // Have to check syntax again when adding 'u' flag.
+
+ // ES 2017 draft rev 9b49a888e9dfe2667008a01b2754c3662059ae56
+ // 21.2.3.2.2 step 7.
+ if (!CheckPatternSyntax(cx, sourceAtom, flagsArg))
+ return false;
+ }
+ flags = flagsArg;
+ }
+
+ regexp->initAndZeroLastIndex(sourceAtom, flags, cx);
+
+ args.rval().setObject(*regexp);
+ return true;
+ }
+
+ RootedValue P(cx);
+ RootedValue F(cx);
+
+ // Step 5.
+ if (patternIsRegExp) {
+ RootedObject patternObj(cx, &patternValue.toObject());
+
+ // Step 5.a.
+ if (!GetProperty(cx, patternObj, patternObj, cx->names().source, &P))
+ return false;
+
+ // Step 5.b.
+ F = args.get(1);
+ if (F.isUndefined()) {
+ if (!GetProperty(cx, patternObj, patternObj, cx->names().flags, &F))
+ return false;
+ }
+ } else {
+ // Steps 6.a-b.
+ P = patternValue;
+ F = args.get(1);
+ }
+
+ // Step 7.
+ RootedObject proto(cx);
+ if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
+ return false;
+
+ Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, proto));
+ if (!regexp)
+ return false;
+
+ // Step 8.
+ if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F))
+ return false;
+ regexp->zeroLastIndex(cx);
+
+ args.rval().setObject(*regexp);
+ return true;
+}
+
+/*
+ * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.3.1
+ * steps 4, 7-8.
+ */
+bool
+js::regexp_construct_raw_flags(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 2);
+ MOZ_ASSERT(!args.isConstructing());
+
+ Rooted<RegExpObject*> rx(cx, &args[0].toObject().as<RegExpObject>());
+
+ // Step 4.a.
+ RootedAtom sourceAtom(cx, rx->getSource());
+
+ // Step 4.c.
+ int32_t flags = int32_t(args[1].toNumber());
+
+ // Step 7.
+ Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx));
+ if (!regexp)
+ return false;
+
+ // Step 8.
+ regexp->initAndZeroLastIndex(sourceAtom, RegExpFlag(flags), cx);
+ args.rval().setObject(*regexp);
+ return true;
+}
+
+bool
+js::regexp_clone(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject from(cx, &args[0].toObject());
+
+ RootedAtom sourceAtom(cx);
+ RegExpFlag flags;
+ {
+ RegExpGuard g(cx);
+ if (!RegExpToShared(cx, from, &g))
+ return false;
+ sourceAtom = g->getSource();
+ flags = g->getFlags();
+ }
+
+ Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx));
+ if (!regexp)
+ return false;
+
+ regexp->initAndZeroLastIndex(sourceAtom, flags, cx);
+
+ args.rval().setObject(*regexp);
+ return true;
+}
+
+/* ES6 draft rev32 21.2.5.4. */
+MOZ_ALWAYS_INLINE bool
+regexp_global_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(IsRegExpObject(args.thisv()));
+ Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
+
+ /* Steps 4-6. */
+ args.rval().setBoolean(reObj->global());
+ return true;
+}
+
+bool
+js::regexp_global(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ /* Steps 1-3. */
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsRegExpObject, regexp_global_impl>(cx, args);
+}
+
+/* ES6 draft rev32 21.2.5.5. */
+MOZ_ALWAYS_INLINE bool
+regexp_ignoreCase_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(IsRegExpObject(args.thisv()));
+ Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
+
+ /* Steps 4-6. */
+ args.rval().setBoolean(reObj->ignoreCase());
+ return true;
+}
+
+bool
+js::regexp_ignoreCase(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ /* Steps 1-3. */
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsRegExpObject, regexp_ignoreCase_impl>(cx, args);
+}
+
+/* ES6 draft rev32 21.2.5.7. */
+MOZ_ALWAYS_INLINE bool
+regexp_multiline_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(IsRegExpObject(args.thisv()));
+ Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
+
+ /* Steps 4-6. */
+ args.rval().setBoolean(reObj->multiline());
+ return true;
+}
+
+bool
+js::regexp_multiline(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ /* Steps 1-3. */
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsRegExpObject, regexp_multiline_impl>(cx, args);
+}
+
+/* ES6 draft rev32 21.2.5.10. */
+MOZ_ALWAYS_INLINE bool
+regexp_source_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(IsRegExpObject(args.thisv()));
+ Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
+
+ /* Step 5. */
+ RootedAtom src(cx, reObj->getSource());
+ if (!src)
+ return false;
+
+ /* Step 7. */
+ RootedString str(cx, EscapeRegExpPattern(cx, src));
+ if (!str)
+ return false;
+
+ args.rval().setString(str);
+ return true;
+}
+
+static bool
+regexp_source(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ /* Steps 1-4. */
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsRegExpObject, regexp_source_impl>(cx, args);
+}
+
+/* ES6 draft rev32 21.2.5.12. */
+MOZ_ALWAYS_INLINE bool
+regexp_sticky_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(IsRegExpObject(args.thisv()));
+ Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
+
+ /* Steps 4-6. */
+ args.rval().setBoolean(reObj->sticky());
+ return true;
+}
+
+bool
+js::regexp_sticky(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ /* Steps 1-3. */
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsRegExpObject, regexp_sticky_impl>(cx, args);
+}
+
+/* ES6 21.2.5.15. */
+MOZ_ALWAYS_INLINE bool
+regexp_unicode_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(IsRegExpObject(args.thisv()));
+ /* Steps 4-6. */
+ args.rval().setBoolean(args.thisv().toObject().as<RegExpObject>().unicode());
+ return true;
+}
+
+bool
+js::regexp_unicode(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ /* Steps 1-3. */
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsRegExpObject, regexp_unicode_impl>(cx, args);
+}
+
+const JSPropertySpec js::regexp_properties[] = {
+ JS_SELF_HOSTED_GET("flags", "RegExpFlagsGetter", 0),
+ JS_PSG("global", regexp_global, 0),
+ JS_PSG("ignoreCase", regexp_ignoreCase, 0),
+ JS_PSG("multiline", regexp_multiline, 0),
+ JS_PSG("source", regexp_source, 0),
+ JS_PSG("sticky", regexp_sticky, 0),
+ JS_PSG("unicode", regexp_unicode, 0),
+ JS_PS_END
+};
+
+const JSFunctionSpec js::regexp_methods[] = {
+#if JS_HAS_TOSOURCE
+ JS_SELF_HOSTED_FN(js_toSource_str, "RegExpToString", 0, 0),
+#endif
+ JS_SELF_HOSTED_FN(js_toString_str, "RegExpToString", 0, 0),
+ JS_FN("compile", regexp_compile, 2,0),
+ JS_SELF_HOSTED_FN("exec", "RegExp_prototype_Exec", 1,0),
+ JS_SELF_HOSTED_FN("test", "RegExpTest" , 1,0),
+ JS_SELF_HOSTED_SYM_FN(match, "RegExpMatch", 1,0),
+ JS_SELF_HOSTED_SYM_FN(replace, "RegExpReplace", 2,0),
+ JS_SELF_HOSTED_SYM_FN(search, "RegExpSearch", 1,0),
+ JS_SELF_HOSTED_SYM_FN(split, "RegExpSplit", 2,0),
+ JS_FS_END
+};
+
+#define STATIC_PAREN_GETTER_CODE(parenNum) \
+ if (!res->createParen(cx, parenNum, args.rval())) \
+ return false; \
+ if (args.rval().isUndefined()) \
+ args.rval().setString(cx->runtime()->emptyString); \
+ return true
+
+/*
+ * RegExp static properties.
+ *
+ * RegExp class static properties and their Perl counterparts:
+ *
+ * RegExp.input $_
+ * RegExp.lastMatch $&
+ * RegExp.lastParen $+
+ * RegExp.leftContext $`
+ * RegExp.rightContext $'
+ */
+
+#define DEFINE_STATIC_GETTER(name, code) \
+ static bool \
+ name(JSContext* cx, unsigned argc, Value* vp) \
+ { \
+ CallArgs args = CallArgsFromVp(argc, vp); \
+ RegExpStatics* res = cx->global()->getRegExpStatics(cx); \
+ if (!res) \
+ return false; \
+ code; \
+ }
+
+DEFINE_STATIC_GETTER(static_input_getter, return res->createPendingInput(cx, args.rval()))
+DEFINE_STATIC_GETTER(static_lastMatch_getter, return res->createLastMatch(cx, args.rval()))
+DEFINE_STATIC_GETTER(static_lastParen_getter, return res->createLastParen(cx, args.rval()))
+DEFINE_STATIC_GETTER(static_leftContext_getter, return res->createLeftContext(cx, args.rval()))
+DEFINE_STATIC_GETTER(static_rightContext_getter, return res->createRightContext(cx, args.rval()))
+
+DEFINE_STATIC_GETTER(static_paren1_getter, STATIC_PAREN_GETTER_CODE(1))
+DEFINE_STATIC_GETTER(static_paren2_getter, STATIC_PAREN_GETTER_CODE(2))
+DEFINE_STATIC_GETTER(static_paren3_getter, STATIC_PAREN_GETTER_CODE(3))
+DEFINE_STATIC_GETTER(static_paren4_getter, STATIC_PAREN_GETTER_CODE(4))
+DEFINE_STATIC_GETTER(static_paren5_getter, STATIC_PAREN_GETTER_CODE(5))
+DEFINE_STATIC_GETTER(static_paren6_getter, STATIC_PAREN_GETTER_CODE(6))
+DEFINE_STATIC_GETTER(static_paren7_getter, STATIC_PAREN_GETTER_CODE(7))
+DEFINE_STATIC_GETTER(static_paren8_getter, STATIC_PAREN_GETTER_CODE(8))
+DEFINE_STATIC_GETTER(static_paren9_getter, STATIC_PAREN_GETTER_CODE(9))
+
+#define DEFINE_STATIC_SETTER(name, code) \
+ static bool \
+ name(JSContext* cx, unsigned argc, Value* vp) \
+ { \
+ RegExpStatics* res = cx->global()->getRegExpStatics(cx); \
+ if (!res) \
+ return false; \
+ code; \
+ return true; \
+ }
+
+static bool
+static_input_setter(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RegExpStatics* res = cx->global()->getRegExpStatics(cx);
+ if (!res)
+ return false;
+
+ RootedString str(cx, ToString<CanGC>(cx, args.get(0)));
+ if (!str)
+ return false;
+
+ res->setPendingInput(str);
+ args.rval().setString(str);
+ return true;
+}
+
+const JSPropertySpec js::regexp_static_props[] = {
+ JS_PSGS("input", static_input_getter, static_input_setter,
+ JSPROP_PERMANENT | JSPROP_ENUMERATE),
+ JS_PSG("lastMatch", static_lastMatch_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
+ JS_PSG("lastParen", static_lastParen_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
+ JS_PSG("leftContext", static_leftContext_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
+ JS_PSG("rightContext", static_rightContext_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
+ JS_PSG("$1", static_paren1_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
+ JS_PSG("$2", static_paren2_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
+ JS_PSG("$3", static_paren3_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
+ JS_PSG("$4", static_paren4_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
+ JS_PSG("$5", static_paren5_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
+ JS_PSG("$6", static_paren6_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
+ JS_PSG("$7", static_paren7_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
+ JS_PSG("$8", static_paren8_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
+ JS_PSG("$9", static_paren9_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
+ JS_PSGS("$_", static_input_getter, static_input_setter, JSPROP_PERMANENT),
+ JS_PSG("$&", static_lastMatch_getter, JSPROP_PERMANENT),
+ JS_PSG("$+", static_lastParen_getter, JSPROP_PERMANENT),
+ JS_PSG("$`", static_leftContext_getter, JSPROP_PERMANENT),
+ JS_PSG("$'", static_rightContext_getter, JSPROP_PERMANENT),
+ JS_SELF_HOSTED_SYM_GET(species, "RegExpSpecies", 0),
+ JS_PS_END
+};
+
+JSObject*
+js::CreateRegExpPrototype(JSContext* cx, JSProtoKey key)
+{
+ MOZ_ASSERT(key == JSProto_RegExp);
+
+ Rooted<RegExpObject*> proto(cx, cx->global()->createBlankPrototype<RegExpObject>(cx));
+ if (!proto)
+ return nullptr;
+ proto->NativeObject::setPrivate(nullptr);
+
+ if (!RegExpObject::assignInitialShape(cx, proto))
+ return nullptr;
+
+ RootedAtom source(cx, cx->names().empty);
+ proto->initAndZeroLastIndex(source, RegExpFlag(0), cx);
+
+ return proto;
+}
+
+template <typename CharT>
+static bool
+IsTrailSurrogateWithLeadSurrogateImpl(JSContext* cx, HandleLinearString input, size_t index)
+{
+ JS::AutoCheckCannotGC nogc;
+ MOZ_ASSERT(index > 0 && index < input->length());
+ const CharT* inputChars = input->chars<CharT>(nogc);
+
+ return unicode::IsTrailSurrogate(inputChars[index]) &&
+ unicode::IsLeadSurrogate(inputChars[index - 1]);
+}
+
+static bool
+IsTrailSurrogateWithLeadSurrogate(JSContext* cx, HandleLinearString input, int32_t index)
+{
+ if (index <= 0 || size_t(index) >= input->length())
+ return false;
+
+ return input->hasLatin1Chars()
+ ? IsTrailSurrogateWithLeadSurrogateImpl<Latin1Char>(cx, input, index)
+ : IsTrailSurrogateWithLeadSurrogateImpl<char16_t>(cx, input, index);
+}
+
+/*
+ * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
+ * steps 3, 9-14, except 12.a.i, 12.c.i.1.
+ */
+static RegExpRunStatus
+ExecuteRegExp(JSContext* cx, HandleObject regexp, HandleString string,
+ int32_t lastIndex,
+ MatchPairs* matches, size_t* endIndex, RegExpStaticsUpdate staticsUpdate)
+{
+ /*
+ * WARNING: Despite the presence of spec step comment numbers, this
+ * algorithm isn't consistent with any ES6 version, draft or
+ * otherwise. YOU HAVE BEEN WARNED.
+ */
+
+ /* Steps 1-2 performed by the caller. */
+ Rooted<RegExpObject*> reobj(cx, &regexp->as<RegExpObject>());
+
+ RegExpGuard re(cx);
+ if (!reobj->getShared(cx, &re))
+ return RegExpRunStatus_Error;
+
+ RegExpStatics* res;
+ if (staticsUpdate == UpdateRegExpStatics) {
+ res = cx->global()->getRegExpStatics(cx);
+ if (!res)
+ return RegExpRunStatus_Error;
+ } else {
+ res = nullptr;
+ }
+
+ RootedLinearString input(cx, string->ensureLinear(cx));
+ if (!input)
+ return RegExpRunStatus_Error;
+
+ /* Handled by caller */
+ MOZ_ASSERT(lastIndex >= 0 && size_t(lastIndex) <= input->length());
+
+ /* Steps 4-8 performed by the caller. */
+
+ /* Step 10. */
+ if (reobj->unicode()) {
+ /*
+ * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad
+ * 21.2.2.2 step 2.
+ * Let listIndex be the index into Input of the character that was
+ * obtained from element index of str.
+ *
+ * In the spec, pattern match is performed with decoded Unicode code
+ * points, but our implementation performs it with UTF-16 encoded
+ * string. In step 2, we should decrement lastIndex (index) if it
+ * points the trail surrogate that has corresponding lead surrogate.
+ *
+ * var r = /\uD83D\uDC38/ug;
+ * r.lastIndex = 1;
+ * var str = "\uD83D\uDC38";
+ * var result = r.exec(str); // pattern match starts from index 0
+ * print(result.index); // prints 0
+ *
+ * Note: this doesn't match the current spec text and result in
+ * different values for `result.index` under certain conditions.
+ * However, the spec will change to match our implementation's
+ * behavior. See https://github.com/tc39/ecma262/issues/128.
+ */
+ if (IsTrailSurrogateWithLeadSurrogate(cx, input, lastIndex))
+ lastIndex--;
+ }
+
+ /* Steps 3, 11-14, except 12.a.i, 12.c.i.1. */
+ RegExpRunStatus status = ExecuteRegExpImpl(cx, res, *re, input, lastIndex, matches, endIndex);
+ if (status == RegExpRunStatus_Error)
+ return RegExpRunStatus_Error;
+
+ /* Steps 12.a.i, 12.c.i.i, 15 are done by Self-hosted function. */
+
+ return status;
+}
+
+/*
+ * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
+ * steps 3, 9-25, except 12.a.i, 12.c.i.1, 15.
+ */
+static bool
+RegExpMatcherImpl(JSContext* cx, HandleObject regexp, HandleString string,
+ int32_t lastIndex, RegExpStaticsUpdate staticsUpdate, MutableHandleValue rval)
+{
+ /* Execute regular expression and gather matches. */
+ ScopedMatchPairs matches(&cx->tempLifoAlloc());
+
+ /* Steps 3, 9-14, except 12.a.i, 12.c.i.1. */
+ RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex,
+ &matches, nullptr, staticsUpdate);
+ if (status == RegExpRunStatus_Error)
+ return false;
+
+ /* Steps 12.a, 12.c. */
+ if (status == RegExpRunStatus_Success_NotFound) {
+ rval.setNull();
+ return true;
+ }
+
+ /* Steps 16-25 */
+ return CreateRegExpMatchResult(cx, string, matches, rval);
+}
+
+/*
+ * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
+ * steps 3, 9-25, except 12.a.i, 12.c.i.1, 15.
+ */
+bool
+js::RegExpMatcher(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 3);
+ MOZ_ASSERT(IsRegExpObject(args[0]));
+ MOZ_ASSERT(args[1].isString());
+ MOZ_ASSERT(args[2].isNumber());
+
+ RootedObject regexp(cx, &args[0].toObject());
+ RootedString string(cx, args[1].toString());
+ RootedValue lastIndexVal(cx, args[2]);
+
+ int32_t lastIndex = 0;
+ if (!ToInt32(cx, lastIndexVal, &lastIndex))
+ return false;
+
+ /* Steps 3, 9-25, except 12.a.i, 12.c.i.1, 15. */
+ return RegExpMatcherImpl(cx, regexp, string, lastIndex,
+ UpdateRegExpStatics, args.rval());
+}
+
+/*
+ * Separate interface for use by IonMonkey.
+ * This code cannot re-enter Ion code.
+ */
+bool
+js::RegExpMatcherRaw(JSContext* cx, HandleObject regexp, HandleString input,
+ int32_t lastIndex,
+ MatchPairs* maybeMatches, MutableHandleValue output)
+{
+ MOZ_ASSERT(lastIndex >= 0);
+
+ // The MatchPairs will always be passed in, but RegExp execution was
+ // successful only if the pairs have actually been filled in.
+ if (maybeMatches && maybeMatches->pairsRaw()[0] >= 0)
+ return CreateRegExpMatchResult(cx, input, *maybeMatches, output);
+ return RegExpMatcherImpl(cx, regexp, input, lastIndex,
+ UpdateRegExpStatics, output);
+}
+
+/*
+ * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
+ * steps 3, 9-25, except 12.a.i, 12.c.i.1, 15.
+ * This code is inlined in CodeGenerator.cpp generateRegExpSearcherStub,
+ * changes to this code need to get reflected in there too.
+ */
+static bool
+RegExpSearcherImpl(JSContext* cx, HandleObject regexp, HandleString string,
+ int32_t lastIndex, RegExpStaticsUpdate staticsUpdate, int32_t* result)
+{
+ /* Execute regular expression and gather matches. */
+ ScopedMatchPairs matches(&cx->tempLifoAlloc());
+
+ /* Steps 3, 9-14, except 12.a.i, 12.c.i.1. */
+ RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex,
+ &matches, nullptr, staticsUpdate);
+ if (status == RegExpRunStatus_Error)
+ return false;
+
+ /* Steps 12.a, 12.c. */
+ if (status == RegExpRunStatus_Success_NotFound) {
+ *result = -1;
+ return true;
+ }
+
+ /* Steps 16-25 */
+ *result = CreateRegExpSearchResult(cx, matches);
+ return true;
+}
+
+/*
+ * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
+ * steps 3, 9-25, except 12.a.i, 12.c.i.1, 15.
+ */
+bool
+js::RegExpSearcher(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 3);
+ MOZ_ASSERT(IsRegExpObject(args[0]));
+ MOZ_ASSERT(args[1].isString());
+ MOZ_ASSERT(args[2].isNumber());
+
+ RootedObject regexp(cx, &args[0].toObject());
+ RootedString string(cx, args[1].toString());
+ RootedValue lastIndexVal(cx, args[2]);
+
+ int32_t lastIndex = 0;
+ if (!ToInt32(cx, lastIndexVal, &lastIndex))
+ return false;
+
+ /* Steps 3, 9-25, except 12.a.i, 12.c.i.1, 15. */
+ int32_t result = 0;
+ if (!RegExpSearcherImpl(cx, regexp, string, lastIndex, UpdateRegExpStatics, &result))
+ return false;
+
+ args.rval().setInt32(result);
+ return true;
+}
+
+/*
+ * Separate interface for use by IonMonkey.
+ * This code cannot re-enter Ion code.
+ */
+bool
+js::RegExpSearcherRaw(JSContext* cx, HandleObject regexp, HandleString input,
+ int32_t lastIndex, MatchPairs* maybeMatches, int32_t* result)
+{
+ MOZ_ASSERT(lastIndex >= 0);
+
+ // The MatchPairs will always be passed in, but RegExp execution was
+ // successful only if the pairs have actually been filled in.
+ if (maybeMatches && maybeMatches->pairsRaw()[0] >= 0) {
+ *result = CreateRegExpSearchResult(cx, *maybeMatches);
+ return true;
+ }
+ return RegExpSearcherImpl(cx, regexp, input, lastIndex,
+ UpdateRegExpStatics, result);
+}
+
+bool
+js::regexp_exec_no_statics(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 2);
+ MOZ_ASSERT(IsRegExpObject(args[0]));
+ MOZ_ASSERT(args[1].isString());
+
+ RootedObject regexp(cx, &args[0].toObject());
+ RootedString string(cx, args[1].toString());
+
+ return RegExpMatcherImpl(cx, regexp, string, 0,
+ DontUpdateRegExpStatics, args.rval());
+}
+
+/*
+ * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2
+ * steps 3, 9-14, except 12.a.i, 12.c.i.1.
+ */
+bool
+js::RegExpTester(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 3);
+ MOZ_ASSERT(IsRegExpObject(args[0]));
+ MOZ_ASSERT(args[1].isString());
+ MOZ_ASSERT(args[2].isNumber());
+
+ RootedObject regexp(cx, &args[0].toObject());
+ RootedString string(cx, args[1].toString());
+ RootedValue lastIndexVal(cx, args[2]);
+
+ int32_t lastIndex = 0;
+ if (!ToInt32(cx, lastIndexVal, &lastIndex))
+ return false;
+
+ /* Steps 3, 9-14, except 12.a.i, 12.c.i.1. */
+ size_t endIndex = 0;
+ RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex,
+ nullptr, &endIndex, UpdateRegExpStatics);
+
+ if (status == RegExpRunStatus_Error)
+ return false;
+
+ if (status == RegExpRunStatus_Success) {
+ MOZ_ASSERT(endIndex <= INT32_MAX);
+ args.rval().setInt32(int32_t(endIndex));
+ } else {
+ args.rval().setInt32(-1);
+ }
+ return true;
+}
+
+/*
+ * Separate interface for use by IonMonkey.
+ * This code cannot re-enter Ion code.
+ */
+bool
+js::RegExpTesterRaw(JSContext* cx, HandleObject regexp, HandleString input,
+ int32_t lastIndex, int32_t* endIndex)
+{
+ MOZ_ASSERT(lastIndex >= 0);
+
+ size_t endIndexTmp = 0;
+ RegExpRunStatus status = ExecuteRegExp(cx, regexp, input, lastIndex,
+ nullptr, &endIndexTmp, UpdateRegExpStatics);
+
+ if (status == RegExpRunStatus_Success) {
+ MOZ_ASSERT(endIndexTmp <= INT32_MAX);
+ *endIndex = int32_t(endIndexTmp);
+ return true;
+ }
+ if (status == RegExpRunStatus_Success_NotFound) {
+ *endIndex = -1;
+ return true;
+ }
+
+ return false;
+}
+
+bool
+js::regexp_test_no_statics(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 2);
+ MOZ_ASSERT(IsRegExpObject(args[0]));
+ MOZ_ASSERT(args[1].isString());
+
+ RootedObject regexp(cx, &args[0].toObject());
+ RootedString string(cx, args[1].toString());
+
+ size_t ignored = 0;
+ RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, 0,
+ nullptr, &ignored, DontUpdateRegExpStatics);
+ args.rval().setBoolean(status == RegExpRunStatus_Success);
+ return status != RegExpRunStatus_Error;
+}
+
+static void
+GetParen(JSLinearString* matched, const JS::Value& capture, JSSubString* out)
+{
+ if (capture.isUndefined()) {
+ out->initEmpty(matched);
+ return;
+ }
+ JSLinearString& captureLinear = capture.toString()->asLinear();
+ out->init(&captureLinear, 0, captureLinear.length());
+}
+
+template <typename CharT>
+static bool
+InterpretDollar(JSLinearString* matched, JSLinearString* string, size_t position, size_t tailPos,
+ MutableHandle<GCVector<Value>> captures, JSLinearString* replacement,
+ const CharT* replacementBegin, const CharT* currentDollar,
+ const CharT* replacementEnd,
+ JSSubString* out, size_t* skip)
+{
+ MOZ_ASSERT(*currentDollar == '$');
+
+ /* If there is only a dollar, bail now. */
+ if (currentDollar + 1 >= replacementEnd)
+ return false;
+
+ /* ES 2016 draft Mar 25, 2016 Table 46. */
+ char16_t c = currentDollar[1];
+ if (JS7_ISDEC(c)) {
+ /* $n, $nn */
+ unsigned num = JS7_UNDEC(c);
+ if (num > captures.length()) {
+ // The result is implementation-defined, do not substitute.
+ return false;
+ }
+
+ const CharT* currentChar = currentDollar + 2;
+ if (currentChar < replacementEnd && (c = *currentChar, JS7_ISDEC(c))) {
+ unsigned tmpNum = 10 * num + JS7_UNDEC(c);
+ // If num > captures.length(), the result is implementation-defined.
+ // Consume next character only if num <= captures.length().
+ if (tmpNum <= captures.length()) {
+ currentChar++;
+ num = tmpNum;
+ }
+ }
+ if (num == 0) {
+ // The result is implementation-defined.
+ // Do not substitute.
+ return false;
+ }
+
+ *skip = currentChar - currentDollar;
+
+ MOZ_ASSERT(num <= captures.length());
+
+ GetParen(matched, captures[num -1], out);
+ return true;
+ }
+
+ *skip = 2;
+ switch (c) {
+ default:
+ return false;
+ case '$':
+ out->init(replacement, currentDollar - replacementBegin, 1);
+ break;
+ case '&':
+ out->init(matched, 0, matched->length());
+ break;
+ case '+':
+ // SpiderMonkey extension
+ if (captures.length() == 0)
+ out->initEmpty(matched);
+ else
+ GetParen(matched, captures[captures.length() - 1], out);
+ break;
+ case '`':
+ out->init(string, 0, position);
+ break;
+ case '\'':
+ out->init(string, tailPos, string->length() - tailPos);
+ break;
+ }
+ return true;
+}
+
+template <typename CharT>
+static bool
+FindReplaceLengthString(JSContext* cx, HandleLinearString matched, HandleLinearString string,
+ size_t position, size_t tailPos, MutableHandle<GCVector<Value>> captures,
+ HandleLinearString replacement, size_t firstDollarIndex, size_t* sizep)
+{
+ CheckedInt<uint32_t> replen = replacement->length();
+
+ JS::AutoCheckCannotGC nogc;
+ MOZ_ASSERT(firstDollarIndex < replacement->length());
+ const CharT* replacementBegin = replacement->chars<CharT>(nogc);
+ const CharT* currentDollar = replacementBegin + firstDollarIndex;
+ const CharT* replacementEnd = replacementBegin + replacement->length();
+ do {
+ JSSubString sub;
+ size_t skip;
+ if (InterpretDollar(matched, string, position, tailPos, captures, replacement,
+ replacementBegin, currentDollar, replacementEnd, &sub, &skip))
+ {
+ if (sub.length > skip)
+ replen += sub.length - skip;
+ else
+ replen -= skip - sub.length;
+ currentDollar += skip;
+ } else {
+ currentDollar++;
+ }
+
+ currentDollar = js_strchr_limit(currentDollar, '$', replacementEnd);
+ } while (currentDollar);
+
+ if (!replen.isValid()) {
+ ReportAllocationOverflow(cx);
+ return false;
+ }
+
+ *sizep = replen.value();
+ return true;
+}
+
+static bool
+FindReplaceLength(JSContext* cx, HandleLinearString matched, HandleLinearString string,
+ size_t position, size_t tailPos, MutableHandle<GCVector<Value>> captures,
+ HandleLinearString replacement, size_t firstDollarIndex, size_t* sizep)
+{
+ return replacement->hasLatin1Chars()
+ ? FindReplaceLengthString<Latin1Char>(cx, matched, string, position, tailPos, captures,
+ replacement, firstDollarIndex, sizep)
+ : FindReplaceLengthString<char16_t>(cx, matched, string, position, tailPos, captures,
+ replacement, firstDollarIndex, sizep);
+}
+
+/*
+ * Precondition: |sb| already has necessary growth space reserved (as
+ * derived from FindReplaceLength), and has been inflated to TwoByte if
+ * necessary.
+ */
+template <typename CharT>
+static void
+DoReplace(HandleLinearString matched, HandleLinearString string,
+ size_t position, size_t tailPos, MutableHandle<GCVector<Value>> captures,
+ HandleLinearString replacement, size_t firstDollarIndex, StringBuffer &sb)
+{
+ JS::AutoCheckCannotGC nogc;
+ const CharT* replacementBegin = replacement->chars<CharT>(nogc);
+ const CharT* currentChar = replacementBegin;
+
+ MOZ_ASSERT(firstDollarIndex < replacement->length());
+ const CharT* currentDollar = replacementBegin + firstDollarIndex;
+ const CharT* replacementEnd = replacementBegin + replacement->length();
+ do {
+ /* Move one of the constant portions of the replacement value. */
+ size_t len = currentDollar - currentChar;
+ sb.infallibleAppend(currentChar, len);
+ currentChar = currentDollar;
+
+ JSSubString sub;
+ size_t skip;
+ if (InterpretDollar(matched, string, position, tailPos, captures, replacement,
+ replacementBegin, currentDollar, replacementEnd, &sub, &skip))
+ {
+ sb.infallibleAppendSubstring(sub.base, sub.offset, sub.length);
+ currentChar += skip;
+ currentDollar += skip;
+ } else {
+ currentDollar++;
+ }
+
+ currentDollar = js_strchr_limit(currentDollar, '$', replacementEnd);
+ } while (currentDollar);
+ sb.infallibleAppend(currentChar, replacement->length() - (currentChar - replacementBegin));
+}
+
+static bool
+NeedTwoBytes(HandleLinearString string, HandleLinearString replacement,
+ HandleLinearString matched, Handle<GCVector<Value>> captures)
+{
+ if (string->hasTwoByteChars())
+ return true;
+ if (replacement->hasTwoByteChars())
+ return true;
+ if (matched->hasTwoByteChars())
+ return true;
+
+ for (size_t i = 0, len = captures.length(); i < len; i++) {
+ Value capture = captures[i];
+ if (capture.isUndefined())
+ continue;
+ if (capture.toString()->hasTwoByteChars())
+ return true;
+ }
+
+ return false;
+}
+
+/* ES 2016 draft Mar 25, 2016 21.1.3.14.1. */
+bool
+js::RegExpGetSubstitution(JSContext* cx, HandleLinearString matched, HandleLinearString string,
+ size_t position, HandleObject capturesObj, HandleLinearString replacement,
+ size_t firstDollarIndex, MutableHandleValue rval)
+{
+ MOZ_ASSERT(firstDollarIndex < replacement->length());
+
+ // Step 1 (skipped).
+
+ // Step 2.
+ size_t matchLength = matched->length();
+
+ // Steps 3-5 (skipped).
+
+ // Step 6.
+ MOZ_ASSERT(position <= string->length());
+
+ // Step 10 (reordered).
+ uint32_t nCaptures;
+ if (!GetLengthProperty(cx, capturesObj, &nCaptures))
+ return false;
+
+ Rooted<GCVector<Value>> captures(cx, GCVector<Value>(cx));
+ if (!captures.reserve(nCaptures))
+ return false;
+
+ // Step 7.
+ RootedValue capture(cx);
+ for (uint32_t i = 0; i < nCaptures; i++) {
+ if (!GetElement(cx, capturesObj, capturesObj, i, &capture))
+ return false;
+
+ if (capture.isUndefined()) {
+ captures.infallibleAppend(capture);
+ continue;
+ }
+
+ MOZ_ASSERT(capture.isString());
+ RootedLinearString captureLinear(cx, capture.toString()->ensureLinear(cx));
+ if (!captureLinear)
+ return false;
+ captures.infallibleAppend(StringValue(captureLinear));
+ }
+
+ // Step 8 (skipped).
+
+ // Step 9.
+ CheckedInt<uint32_t> checkedTailPos(0);
+ checkedTailPos += position;
+ checkedTailPos += matchLength;
+ if (!checkedTailPos.isValid()) {
+ ReportAllocationOverflow(cx);
+ return false;
+ }
+ uint32_t tailPos = checkedTailPos.value();
+
+ // Step 11.
+ size_t reserveLength;
+ if (!FindReplaceLength(cx, matched, string, position, tailPos, &captures, replacement,
+ firstDollarIndex, &reserveLength))
+ {
+ return false;
+ }
+
+ StringBuffer result(cx);
+ if (NeedTwoBytes(string, replacement, matched, captures)) {
+ if (!result.ensureTwoByteChars())
+ return false;
+ }
+
+ if (!result.reserve(reserveLength))
+ return false;
+
+ if (replacement->hasLatin1Chars()) {
+ DoReplace<Latin1Char>(matched, string, position, tailPos, &captures,
+ replacement, firstDollarIndex, result);
+ } else {
+ DoReplace<char16_t>(matched, string, position, tailPos, &captures,
+ replacement, firstDollarIndex, result);
+ }
+
+ // Step 12.
+ JSString* resultString = result.finishString();
+ if (!resultString)
+ return false;
+
+ rval.setString(resultString);
+ return true;
+}
+
+bool
+js::GetFirstDollarIndex(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ RootedString str(cx, args[0].toString());
+
+ // Should be handled in different path.
+ MOZ_ASSERT(str->length() != 0);
+
+ int32_t index = -1;
+ if (!GetFirstDollarIndexRaw(cx, str, &index))
+ return false;
+
+ args.rval().setInt32(index);
+ return true;
+}
+
+template <typename TextChar>
+static MOZ_ALWAYS_INLINE int
+GetFirstDollarIndexImpl(const TextChar* text, uint32_t textLen)
+{
+ const TextChar* end = text + textLen;
+ for (const TextChar* c = text; c != end; ++c) {
+ if (*c == '$')
+ return c - text;
+ }
+ return -1;
+}
+
+int32_t
+js::GetFirstDollarIndexRawFlat(JSLinearString* text)
+{
+ uint32_t len = text->length();
+
+ JS::AutoCheckCannotGC nogc;
+ if (text->hasLatin1Chars())
+ return GetFirstDollarIndexImpl(text->latin1Chars(nogc), len);
+
+ return GetFirstDollarIndexImpl(text->twoByteChars(nogc), len);
+}
+
+bool
+js::GetFirstDollarIndexRaw(JSContext* cx, HandleString str, int32_t* index)
+{
+ JSLinearString* text = str->ensureLinear(cx);
+ if (!text)
+ return false;
+
+ *index = GetFirstDollarIndexRawFlat(text);
+ return true;
+}
+
+bool
+js::RegExpPrototypeOptimizable(JSContext* cx, unsigned argc, Value* vp)
+{
+ // This can only be called from self-hosted code.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+
+ args.rval().setBoolean(RegExpPrototypeOptimizableRaw(cx, &args[0].toObject()));
+ return true;
+}
+
+bool
+js::RegExpPrototypeOptimizableRaw(JSContext* cx, JSObject* proto)
+{
+ JS::AutoCheckCannotGC nogc;
+ AutoAssertNoPendingException aanpe(cx);
+ if (!proto->isNative())
+ return false;
+
+ NativeObject* nproto = static_cast<NativeObject*>(proto);
+
+ Shape* shape = cx->compartment()->regExps.getOptimizableRegExpPrototypeShape();
+ if (shape == nproto->lastProperty())
+ return true;
+
+ JSFunction* flagsGetter;
+ if (!GetOwnGetterPure(cx, proto, NameToId(cx->names().flags), &flagsGetter))
+ return false;
+
+ if (!flagsGetter)
+ return false;
+
+ if (!IsSelfHostedFunctionWithName(flagsGetter, cx->names().RegExpFlagsGetter))
+ return false;
+
+ JSNative globalGetter;
+ if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().global), &globalGetter))
+ return false;
+
+ if (globalGetter != regexp_global)
+ return false;
+
+ JSNative ignoreCaseGetter;
+ if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().ignoreCase), &ignoreCaseGetter))
+ return false;
+
+ if (ignoreCaseGetter != regexp_ignoreCase)
+ return false;
+
+ JSNative multilineGetter;
+ if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().multiline), &multilineGetter))
+ return false;
+
+ if (multilineGetter != regexp_multiline)
+ return false;
+
+ JSNative stickyGetter;
+ if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().sticky), &stickyGetter))
+ return false;
+
+ if (stickyGetter != regexp_sticky)
+ return false;
+
+ JSNative unicodeGetter;
+ if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().unicode), &unicodeGetter))
+ return false;
+
+ if (unicodeGetter != regexp_unicode)
+ return false;
+
+ // Check if @@match, @@search, and exec are own data properties,
+ // those values should be tested in selfhosted JS.
+ bool has = false;
+ if (!HasOwnDataPropertyPure(cx, proto, SYMBOL_TO_JSID(cx->wellKnownSymbols().match), &has))
+ return false;
+ if (!has)
+ return false;
+
+ if (!HasOwnDataPropertyPure(cx, proto, SYMBOL_TO_JSID(cx->wellKnownSymbols().search), &has))
+ return false;
+ if (!has)
+ return false;
+
+ if (!HasOwnDataPropertyPure(cx, proto, NameToId(cx->names().exec), &has))
+ return false;
+ if (!has)
+ return false;
+
+ cx->compartment()->regExps.setOptimizableRegExpPrototypeShape(nproto->lastProperty());
+ return true;
+}
+
+bool
+js::RegExpInstanceOptimizable(JSContext* cx, unsigned argc, Value* vp)
+{
+ // This can only be called from self-hosted code.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 2);
+
+ args.rval().setBoolean(RegExpInstanceOptimizableRaw(cx, &args[0].toObject(),
+ &args[1].toObject()));
+ return true;
+}
+
+bool
+js::RegExpInstanceOptimizableRaw(JSContext* cx, JSObject* obj, JSObject* proto)
+{
+ JS::AutoCheckCannotGC nogc;
+ AutoAssertNoPendingException aanpe(cx);
+
+ RegExpObject* rx = &obj->as<RegExpObject>();
+
+ Shape* shape = cx->compartment()->regExps.getOptimizableRegExpInstanceShape();
+ if (shape == rx->lastProperty())
+ return true;
+
+ if (!rx->hasStaticPrototype())
+ return false;
+
+ if (rx->staticPrototype() != proto)
+ return false;
+
+ if (!RegExpObject::isInitialShape(rx))
+ return false;
+
+ cx->compartment()->regExps.setOptimizableRegExpInstanceShape(rx->lastProperty());
+ return true;
+}
+
+/*
+ * Pattern match the script to check if it is is indexing into a particular
+ * object, e.g. 'function(a) { return b[a]; }'. Avoid calling the script in
+ * such cases, which are used by javascript packers (particularly the popular
+ * Dean Edwards packer) to efficiently encode large scripts. We only handle the
+ * code patterns generated by such packers here.
+ */
+bool
+js::intrinsic_GetElemBaseForLambda(JSContext* cx, unsigned argc, Value* vp)
+{
+ // This can only be called from self-hosted code.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+
+ JSObject& lambda = args[0].toObject();
+ args.rval().setUndefined();
+
+ if (!lambda.is<JSFunction>())
+ return true;
+
+ RootedFunction fun(cx, &lambda.as<JSFunction>());
+ if (!fun->isInterpreted() || fun->isClassConstructor())
+ return true;
+
+ JSScript* script = fun->getOrCreateScript(cx);
+ if (!script)
+ return false;
+
+ jsbytecode* pc = script->code();
+
+ /*
+ * JSOP_GETALIASEDVAR tells us exactly where to find the base object 'b'.
+ * Rule out the (unlikely) possibility of a function with environment
+ * objects since it would make our environment walk off.
+ */
+ if (JSOp(*pc) != JSOP_GETALIASEDVAR || fun->needsSomeEnvironmentObject())
+ return true;
+ EnvironmentCoordinate ec(pc);
+ EnvironmentObject* env = &fun->environment()->as<EnvironmentObject>();
+ for (unsigned i = 0; i < ec.hops(); ++i)
+ env = &env->enclosingEnvironment().as<EnvironmentObject>();
+ Value b = env->aliasedBinding(ec);
+ pc += JSOP_GETALIASEDVAR_LENGTH;
+
+ /* Look for 'a' to be the lambda's first argument. */
+ if (JSOp(*pc) != JSOP_GETARG || GET_ARGNO(pc) != 0)
+ return true;
+ pc += JSOP_GETARG_LENGTH;
+
+ /* 'b[a]' */
+ if (JSOp(*pc) != JSOP_GETELEM)
+ return true;
+ pc += JSOP_GETELEM_LENGTH;
+
+ /* 'return b[a]' */
+ if (JSOp(*pc) != JSOP_RETURN)
+ return true;
+
+ /* 'b' must behave like a normal object. */
+ if (!b.isObject())
+ return true;
+
+ JSObject& bobj = b.toObject();
+ const Class* clasp = bobj.getClass();
+ if (!clasp->isNative() || clasp->getOpsLookupProperty() || clasp->getOpsGetProperty())
+ return true;
+
+ args.rval().setObject(bobj);
+ return true;
+}
+
+/*
+ * Emulates `b[a]` property access, that is detected in GetElemBaseForLambda.
+ * It returns the property value only if the property is data property and the
+ * propety value is a string. Otherwise it returns undefined.
+ */
+bool
+js::intrinsic_GetStringDataProperty(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 2);
+
+ RootedObject obj(cx, &args[0].toObject());
+ if (!obj->isNative()) {
+ // The object is already checked to be native in GetElemBaseForLambda,
+ // but it can be swapped to the other class that is non-native.
+ // Return undefined to mark failure to get the property.
+ args.rval().setUndefined();
+ return true;
+ }
+
+ RootedNativeObject nobj(cx, &obj->as<NativeObject>());
+ RootedString name(cx, args[1].toString());
+
+ RootedAtom atom(cx, AtomizeString(cx, name));
+ if (!atom)
+ return false;
+
+ RootedValue v(cx);
+ if (HasDataProperty(cx, nobj, AtomToId(atom), v.address()) && v.isString())
+ args.rval().set(v);
+ else
+ args.rval().setUndefined();
+
+ return true;
+}
diff --git a/js/src/builtin/RegExp.h b/js/src/builtin/RegExp.h
new file mode 100644
index 000000000..715656f40
--- /dev/null
+++ b/js/src/builtin/RegExp.h
@@ -0,0 +1,159 @@
+/* -*- 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/. */
+
+#ifndef builtin_RegExp_h
+#define builtin_RegExp_h
+
+#include "vm/RegExpObject.h"
+
+/*
+ * The following builtin natives are extern'd for pointer comparison in
+ * other parts of the engine.
+ */
+
+namespace js {
+
+JSObject*
+InitRegExpClass(JSContext* cx, HandleObject obj);
+
+// Whether RegExp statics should be updated with the input and results of a
+// regular expression execution.
+enum RegExpStaticsUpdate { UpdateRegExpStatics, DontUpdateRegExpStatics };
+
+/*
+ * Legacy behavior of ExecuteRegExp(), which is baked into the JSAPI.
+ *
+ * |res| may be nullptr if the RegExpStatics are not to be updated.
+ * |input| may be nullptr if there is no JSString corresponding to
+ * |chars| and |length|.
+ */
+MOZ_MUST_USE bool
+ExecuteRegExpLegacy(JSContext* cx, RegExpStatics* res, RegExpObject& reobj,
+ HandleLinearString input, size_t* lastIndex, bool test,
+ MutableHandleValue rval);
+
+/* Translation from MatchPairs to a JS array in regexp_exec()'s output format. */
+MOZ_MUST_USE bool
+CreateRegExpMatchResult(JSContext* cx, HandleString input, const MatchPairs& matches,
+ MutableHandleValue rval);
+
+extern MOZ_MUST_USE bool
+RegExpMatcher(JSContext* cx, unsigned argc, Value* vp);
+
+extern MOZ_MUST_USE bool
+RegExpMatcherRaw(JSContext* cx, HandleObject regexp, HandleString input,
+ int32_t lastIndex, MatchPairs* maybeMatches, MutableHandleValue output);
+
+extern MOZ_MUST_USE bool
+RegExpSearcher(JSContext* cx, unsigned argc, Value* vp);
+
+extern MOZ_MUST_USE bool
+RegExpSearcherRaw(JSContext* cx, HandleObject regexp, HandleString input,
+ int32_t lastIndex, MatchPairs* maybeMatches, int32_t* result);
+
+extern MOZ_MUST_USE bool
+RegExpTester(JSContext* cx, unsigned argc, Value* vp);
+
+extern MOZ_MUST_USE bool
+RegExpTesterRaw(JSContext* cx, HandleObject regexp, HandleString input,
+ int32_t lastIndex, int32_t* endIndex);
+
+extern MOZ_MUST_USE bool
+intrinsic_GetElemBaseForLambda(JSContext* cx, unsigned argc, Value* vp);
+
+extern MOZ_MUST_USE bool
+intrinsic_GetStringDataProperty(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * The following functions are for use by self-hosted code.
+ */
+
+/*
+ * Behaves like regexp.exec(string), but doesn't set RegExp statics.
+ *
+ * Usage: match = regexp_exec_no_statics(regexp, string)
+ */
+extern MOZ_MUST_USE bool
+regexp_exec_no_statics(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Behaves like regexp.test(string), but doesn't set RegExp statics.
+ *
+ * Usage: does_match = regexp_test_no_statics(regexp, string)
+ */
+extern MOZ_MUST_USE bool
+regexp_test_no_statics(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Behaves like RegExp(pattern, flags).
+ * |pattern| should be a RegExp object, |flags| should be a raw integer value.
+ * Must be called without |new|.
+ * Dedicated function for RegExp.prototype[@@split] optimized path.
+ */
+extern MOZ_MUST_USE bool
+regexp_construct_raw_flags(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Clone given RegExp object, inheriting pattern and flags, ignoring other
+ * properties.
+ */
+extern MOZ_MUST_USE bool
+regexp_clone(JSContext* cx, unsigned argc, Value* vp);
+
+extern MOZ_MUST_USE bool
+IsRegExp(JSContext* cx, HandleValue value, bool* result);
+
+extern MOZ_MUST_USE bool
+RegExpCreate(JSContext* cx, HandleValue pattern, HandleValue flags, MutableHandleValue rval);
+
+extern MOZ_MUST_USE bool
+RegExpPrototypeOptimizable(JSContext* cx, unsigned argc, Value* vp);
+
+extern MOZ_MUST_USE bool
+RegExpPrototypeOptimizableRaw(JSContext* cx, JSObject* proto);
+
+extern MOZ_MUST_USE bool
+RegExpInstanceOptimizable(JSContext* cx, unsigned argc, Value* vp);
+
+extern MOZ_MUST_USE bool
+RegExpInstanceOptimizableRaw(JSContext* cx, JSObject* obj, JSObject* proto);
+
+extern MOZ_MUST_USE bool
+RegExpGetSubstitution(JSContext* cx, HandleLinearString matched, HandleLinearString string,
+ size_t position, HandleObject capturesObj, HandleLinearString replacement,
+ size_t firstDollarIndex, MutableHandleValue rval);
+
+extern MOZ_MUST_USE bool
+GetFirstDollarIndex(JSContext* cx, unsigned argc, Value* vp);
+
+extern MOZ_MUST_USE bool
+GetFirstDollarIndexRaw(JSContext* cx, HandleString str, int32_t* index);
+
+extern int32_t
+GetFirstDollarIndexRawFlat(JSLinearString* text);
+
+// RegExp ClassSpec members used in RegExpObject.cpp.
+extern MOZ_MUST_USE bool
+regexp_construct(JSContext* cx, unsigned argc, Value* vp);
+extern const JSPropertySpec regexp_static_props[];
+extern const JSPropertySpec regexp_properties[];
+extern const JSFunctionSpec regexp_methods[];
+
+// Used in RegExpObject::isOriginalFlagGetter.
+extern MOZ_MUST_USE bool
+regexp_global(JSContext* cx, unsigned argc, JS::Value* vp);
+extern MOZ_MUST_USE bool
+regexp_ignoreCase(JSContext* cx, unsigned argc, JS::Value* vp);
+extern MOZ_MUST_USE bool
+regexp_multiline(JSContext* cx, unsigned argc, JS::Value* vp);
+extern MOZ_MUST_USE bool
+regexp_sticky(JSContext* cx, unsigned argc, JS::Value* vp);
+extern MOZ_MUST_USE bool
+regexp_unicode(JSContext* cx, unsigned argc, JS::Value* vp);
+
+} /* namespace js */
+
+#endif /* builtin_RegExp_h */
diff --git a/js/src/builtin/RegExp.js b/js/src/builtin/RegExp.js
new file mode 100644
index 000000000..1ffea0105
--- /dev/null
+++ b/js/src/builtin/RegExp.js
@@ -0,0 +1,1032 @@
+/* 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/. */
+
+// ES6 draft rev34 (2015/02/20) 21.2.5.3 get RegExp.prototype.flags
+function RegExpFlagsGetter() {
+ // Steps 1-2.
+ var R = this;
+ if (!IsObject(R))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, R === null ? "null" : typeof R);
+
+ // Step 3.
+ var result = "";
+
+ // Steps 4-6.
+ if (R.global)
+ result += "g";
+
+ // Steps 7-9.
+ if (R.ignoreCase)
+ result += "i";
+
+ // Steps 10-12.
+ if (R.multiline)
+ result += "m";
+
+ // Steps 13-15.
+ if (R.unicode)
+ result += "u";
+
+ // Steps 16-18.
+ if (R.sticky)
+ result += "y";
+
+ // Step 19.
+ return result;
+}
+_SetCanonicalName(RegExpFlagsGetter, "get flags");
+
+// ES 2017 draft 40edb3a95a475c1b251141ac681b8793129d9a6d 21.2.5.14.
+function RegExpToString()
+{
+ // Step 1.
+ var R = this;
+
+ // Step 2.
+ if (!IsObject(R))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, R === null ? "null" : typeof R);
+
+ // Step 3.
+ var pattern = ToString(R.source);
+
+ // Step 4.
+ var flags = ToString(R.flags);
+
+ // Steps 5-6.
+ return '/' + pattern + '/' + flags;
+}
+_SetCanonicalName(RegExpToString, "toString");
+
+// ES 2016 draft Mar 25, 2016 21.2.5.2.3.
+function AdvanceStringIndex(S, index) {
+ // Step 1.
+ assert(typeof S === "string", "Expected string as 1st argument");
+
+ // Step 2.
+ assert(index >= 0 && index <= MAX_NUMERIC_INDEX, "Expected integer as 2nd argument");
+
+ // Step 3 (skipped).
+
+ // Step 4 (skipped).
+
+ // Step 5.
+ var length = S.length;
+
+ // Step 6.
+ if (index + 1 >= length)
+ return index + 1;
+
+ // Step 7.
+ var first = callFunction(std_String_charCodeAt, S, index);
+
+ // Step 8.
+ if (first < 0xD800 || first > 0xDBFF)
+ return index + 1;
+
+ // Step 9.
+ var second = callFunction(std_String_charCodeAt, S, index + 1);
+
+ // Step 10.
+ if (second < 0xDC00 || second > 0xDFFF)
+ return index + 1;
+
+ // Step 11.
+ return index + 2;
+}
+
+// ES 2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.6.
+function RegExpMatch(string) {
+ // Step 1.
+ var rx = this;
+
+ // Step 2.
+ if (!IsObject(rx))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, rx === null ? "null" : typeof rx);
+
+ // Step 3.
+ var S = ToString(string);
+
+ // Optimized paths for simple cases.
+ if (IsRegExpMethodOptimizable(rx)) {
+ // Step 4.
+ var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT);
+ var global = !!(flags & REGEXP_GLOBAL_FLAG);
+
+ if (global) {
+ // Step 6.a.
+ var fullUnicode = !!(flags & REGEXP_UNICODE_FLAG);
+
+ // Steps 6.b-e.
+ return RegExpGlobalMatchOpt(rx, S, fullUnicode);
+ }
+
+ // Step 5.
+ var sticky = !!(flags & REGEXP_STICKY_FLAG);
+ return RegExpLocalMatchOpt(rx, S, sticky);
+ }
+
+ // Stes 4-6
+ return RegExpMatchSlowPath(rx, S);
+}
+
+// ES 2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.6
+// steps 4-6.
+function RegExpMatchSlowPath(rx, S) {
+ // Steps 4-5.
+ if (!rx.global)
+ return RegExpExec(rx, S, false);
+
+ // Step 6.a.
+ var fullUnicode = !!rx.unicode;
+
+ // Step 6.b.
+ rx.lastIndex = 0;
+
+ // Step 6.c.
+ var A = [];
+
+ // Step 6.d.
+ var n = 0;
+
+ // Step 6.e.
+ while (true) {
+ // Step 6.e.i.
+ var result = RegExpExec(rx, S, false);
+
+ // Step 6.e.ii.
+ if (result === null)
+ return (n === 0) ? null : A;
+
+ // Step 6.e.iii.1.
+ var matchStr = ToString(result[0]);
+
+ // Step 6.e.iii.2.
+ _DefineDataProperty(A, n, matchStr);
+
+ // Step 6.e.iii.4.
+ if (matchStr === "") {
+ var lastIndex = ToLength(rx.lastIndex);
+ rx.lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1;
+ }
+
+ // Step 6.e.iii.5.
+ n++;
+ }
+}
+
+// ES 2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.6.
+// Steps 6.b-e.
+// Optimized path for @@match with global flag.
+function RegExpGlobalMatchOpt(rx, S, fullUnicode) {
+ // Step 6.b.
+ var lastIndex = 0;
+ rx.lastIndex = 0;
+
+ // Step 6.c.
+ var A = [];
+
+ // Step 6.d.
+ var n = 0;
+
+ var lengthS = S.length;
+
+ // Step 6.e.
+ while (true) {
+ // Step 6.e.i.
+ var result = RegExpMatcher(rx, S, lastIndex);
+
+ // Step 6.e.ii.
+ if (result === null)
+ return (n === 0) ? null : A;
+
+ lastIndex = result.index + result[0].length;
+
+ // Step 6.e.iii.1.
+ var matchStr = result[0];
+
+ // Step 6.e.iii.2.
+ _DefineDataProperty(A, n, matchStr);
+
+ // Step 6.e.iii.4.
+ if (matchStr === "") {
+ lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1;
+ if (lastIndex > lengthS)
+ return A;
+ }
+
+ // Step 6.e.iii.5.
+ n++;
+ }
+}
+
+// ES 2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.6 step 5.
+// Optimized path for @@match without global flag.
+function RegExpLocalMatchOpt(rx, S, sticky) {
+ // Step 4.
+ var lastIndex = ToLength(rx.lastIndex);
+
+ // Step 8.
+ if (!sticky) {
+ lastIndex = 0;
+ } else {
+ if (lastIndex > S.length) {
+ // Steps 12.a.i-ii, 12.c.i.1-2.
+ rx.lastIndex = 0;
+ return null;
+ }
+ }
+
+ // Steps 3, 9-25, except 12.a.i-ii, 12.c.i.1-2, 15.
+ var result = RegExpMatcher(rx, S, lastIndex);
+ if (result === null) {
+ // Steps 12.a.i-ii, 12.c.i.1-2.
+ rx.lastIndex = 0;
+ } else {
+ // Step 15.
+ if (sticky)
+ rx.lastIndex = result.index + result[0].length;
+ }
+
+ return result;
+}
+
+// Checks if following properties and getters are not modified, and accessing
+// them not observed by content script:
+// * flags
+// * global
+// * ignoreCase
+// * multiline
+// * sticky
+// * unicode
+// * exec
+// * lastIndex
+function IsRegExpMethodOptimizable(rx) {
+ if (!IsRegExpObject(rx))
+ return false;
+
+ var RegExpProto = GetBuiltinPrototype("RegExp");
+ // If RegExpPrototypeOptimizable and RegExpInstanceOptimizable succeed,
+ // `RegExpProto.exec` is guaranteed to be data properties.
+ return RegExpPrototypeOptimizable(RegExpProto) &&
+ RegExpInstanceOptimizable(rx, RegExpProto) &&
+ RegExpProto.exec === RegExp_prototype_Exec;
+}
+
+// ES 2017 draft rev 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8.
+function RegExpReplace(string, replaceValue) {
+ // Step 1.
+ var rx = this;
+
+ // Step 2.
+ if (!IsObject(rx))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, rx === null ? "null" : typeof rx);
+
+ // Step 3.
+ var S = ToString(string);
+
+ // Step 4.
+ var lengthS = S.length;
+
+ // Step 5.
+ var functionalReplace = IsCallable(replaceValue);
+
+ // Step 6.
+ var firstDollarIndex = -1;
+ if (!functionalReplace) {
+ // Step 6.a.
+ replaceValue = ToString(replaceValue);
+
+ // Skip if replaceValue is an empty string or a single character.
+ // A single character string may contain "$", but that cannot be a
+ // substitution.
+ if (replaceValue.length > 1)
+ firstDollarIndex = GetFirstDollarIndex(replaceValue);
+ }
+
+ // Optimized paths.
+ if (IsRegExpMethodOptimizable(rx)) {
+ var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT);
+
+ // Step 7.
+ var global = !!(flags & REGEXP_GLOBAL_FLAG);
+
+ // Steps 8-16.
+ if (global) {
+ // Step 8.a.
+ var fullUnicode = !!(flags & REGEXP_UNICODE_FLAG);
+
+ if (functionalReplace) {
+ var elemBase = GetElemBaseForLambda(replaceValue);
+ if (IsObject(elemBase))
+ return RegExpGlobalReplaceOptElemBase(rx, S, lengthS, replaceValue,
+ fullUnicode, elemBase);
+ return RegExpGlobalReplaceOptFunc(rx, S, lengthS, replaceValue,
+ fullUnicode);
+ }
+ if (firstDollarIndex !== -1) {
+ return RegExpGlobalReplaceOptSubst(rx, S, lengthS, replaceValue,
+ fullUnicode, firstDollarIndex);
+ }
+ if (lengthS < 0x7fff) {
+ return RegExpGlobalReplaceShortOpt(rx, S, lengthS, replaceValue,
+ fullUnicode);
+ }
+ return RegExpGlobalReplaceOpt(rx, S, lengthS, replaceValue,
+ fullUnicode);
+ }
+
+ var sticky = !!(flags & REGEXP_STICKY_FLAG);
+
+ if (functionalReplace) {
+ return RegExpLocalReplaceOptFunc(rx, S, lengthS, replaceValue,
+ sticky);
+ }
+ if (firstDollarIndex !== -1) {
+ return RegExpLocalReplaceOptSubst(rx, S, lengthS, replaceValue,
+ sticky, firstDollarIndex);
+ }
+ return RegExpLocalReplaceOpt(rx, S, lengthS, replaceValue,
+ sticky);
+ }
+
+ // Steps 8-16.
+ return RegExpReplaceSlowPath(rx, S, lengthS, replaceValue,
+ functionalReplace, firstDollarIndex);
+}
+
+// ES 2017 draft rev 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8
+// steps 7-16.
+// Slow path for @@replace.
+function RegExpReplaceSlowPath(rx, S, lengthS, replaceValue,
+ functionalReplace, firstDollarIndex)
+{
+ // Step 7.
+ var global = !!rx.global;
+
+ // Step 8.
+ var fullUnicode = false;
+ if (global) {
+ // Step 8.a.
+ fullUnicode = !!rx.unicode;
+
+ // Step 8.b.
+ rx.lastIndex = 0;
+ }
+
+ // Step 9.
+ var results = [];
+ var nResults = 0;
+
+ // Step 11.
+ while (true) {
+ // Step 11.a.
+ var result = RegExpExec(rx, S, false);
+
+ // Step 11.b.
+ if (result === null)
+ break;
+
+ // Step 11.c.i.
+ _DefineDataProperty(results, nResults++, result);
+
+ // Step 11.c.ii.
+ if (!global)
+ break;
+
+ // Step 11.c.iii.1.
+ var matchStr = ToString(result[0]);
+
+ // Step 11.c.iii.2.
+ if (matchStr === "") {
+ var lastIndex = ToLength(rx.lastIndex);
+ rx.lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1;
+ }
+ }
+
+ // Step 12.
+ var accumulatedResult = "";
+
+ // Step 13.
+ var nextSourcePosition = 0;
+
+ // Step 14.
+ for (var i = 0; i < nResults; i++) {
+ result = results[i];
+
+ // Steps 14.a-b.
+ var nCaptures = std_Math_max(ToLength(result.length) - 1, 0);
+
+ // Step 14.c.
+ var matched = ToString(result[0]);
+
+ // Step 14.d.
+ var matchLength = matched.length;
+
+ // Steps 14.e-f.
+ var position = std_Math_max(std_Math_min(ToInteger(result.index), lengthS), 0);
+
+ var n, capN, replacement;
+ if (functionalReplace || firstDollarIndex !== -1) {
+ // Steps 14.g-j.
+ replacement = RegExpGetComplexReplacement(result, matched, S, position,
+
+ nCaptures, replaceValue,
+ functionalReplace, firstDollarIndex);
+ } else {
+ // Step 14.g, 14.i, 14.i.iv.
+ // We don't need captures array, but ToString is visible to script.
+ for (n = 1; n <= nCaptures; n++) {
+ // Step 14.i.i-ii.
+ capN = result[n];
+
+ // Step 14.i.ii.
+ if (capN !== undefined)
+ ToString(capN);
+ }
+ replacement = replaceValue;
+ }
+
+ // Step 14.l.
+ if (position >= nextSourcePosition) {
+ // Step 14.l.ii.
+ accumulatedResult += Substring(S, nextSourcePosition,
+ position - nextSourcePosition) + replacement;
+
+ // Step 14.l.iii.
+ nextSourcePosition = position + matchLength;
+ }
+ }
+
+ // Step 15.
+ if (nextSourcePosition >= lengthS)
+ return accumulatedResult;
+
+ // Step 16.
+ return accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition);
+}
+
+// ES 2017 draft rev 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8
+// steps 14.g-k.
+// Calculates functional/substitution replaceement from match result.
+// Used in the following functions:
+// * RegExpGlobalReplaceOptFunc
+// * RegExpGlobalReplaceOptElemBase
+// * RegExpGlobalReplaceOptSubst
+// * RegExpLocalReplaceOptFunc
+// * RegExpLocalReplaceOptSubst
+// * RegExpReplaceSlowPath
+function RegExpGetComplexReplacement(result, matched, S, position,
+ nCaptures, replaceValue,
+ functionalReplace, firstDollarIndex)
+{
+ // Step 14.h.
+ var captures = [];
+ var capturesLength = 0;
+
+ // Step 14.j.i (reordered).
+ // For `nCaptures` <= 4 case, call `replaceValue` directly, otherwise
+ // use `std_Function_apply` with all arguments stored in `captures`.
+ // In latter case, store `matched` as the first element here, to
+ // avoid unshift later.
+ if (functionalReplace && nCaptures > 4)
+ _DefineDataProperty(captures, capturesLength++, matched);
+
+ // Step 14.g, 14.i, 14.i.iv.
+ for (var n = 1; n <= nCaptures; n++) {
+ // Step 14.i.i.
+ var capN = result[n];
+
+ // Step 14.i.ii.
+ if (capN !== undefined)
+ capN = ToString(capN);
+
+ // Step 14.i.iii.
+ _DefineDataProperty(captures, capturesLength++, capN);
+ }
+
+ // Step 14.j.
+ if (functionalReplace) {
+ switch (nCaptures) {
+ case 0:
+ return ToString(replaceValue(matched, position, S));
+ case 1:
+ return ToString(replaceValue(matched, SPREAD(captures, 1), position, S));
+ case 2:
+ return ToString(replaceValue(matched, SPREAD(captures, 2), position, S));
+ case 3:
+ return ToString(replaceValue(matched, SPREAD(captures, 3), position, S));
+ case 4:
+ return ToString(replaceValue(matched, SPREAD(captures, 4), position, S));
+ default:
+ // Steps 14.j.ii-v.
+ _DefineDataProperty(captures, capturesLength++, position);
+ _DefineDataProperty(captures, capturesLength++, S);
+ return ToString(callFunction(std_Function_apply, replaceValue, null, captures));
+ }
+ }
+
+ // Steps 14.k.i.
+ return RegExpGetSubstitution(matched, S, position, captures, replaceValue,
+ firstDollarIndex);
+}
+
+// ES 2017 draft rev 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8
+// steps 8.b-16.
+// Optimized path for @@replace with the following conditions:
+// * global flag is true
+// * S is a short string (lengthS < 0x7fff)
+// * replaceValue is a string without "$"
+function RegExpGlobalReplaceShortOpt(rx, S, lengthS, replaceValue, fullUnicode)
+{
+ // Step 8.b.
+ var lastIndex = 0;
+ rx.lastIndex = 0;
+
+ // Step 12 (reordered).
+ var accumulatedResult = "";
+
+ // Step 13 (reordered).
+ var nextSourcePosition = 0;
+
+ // Step 11.
+ while (true) {
+ // Step 11.a.
+ var result = RegExpSearcher(rx, S, lastIndex);
+
+ // Step 11.b.
+ if (result === -1)
+ break;
+
+ var position = result & 0x7fff;
+ lastIndex = (result >> 15) & 0x7fff;
+
+ // Step 14.l.ii.
+ accumulatedResult += Substring(S, nextSourcePosition,
+ position - nextSourcePosition) + replaceValue;
+
+ // Step 14.l.iii.
+ nextSourcePosition = lastIndex;
+
+ // Step 11.c.iii.2.
+ if (lastIndex === position) {
+ lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1;
+ if (lastIndex > lengthS)
+ break;
+ }
+ }
+
+ // Step 15.
+ if (nextSourcePosition >= lengthS)
+ return accumulatedResult;
+
+ // Step 16.
+ return accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition);
+}
+
+// ES 2017 draft rev 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8
+// steps 8-16.
+// Optimized path for @@replace.
+
+// Conditions:
+// * global flag is true
+// * replaceValue is a string without "$"
+#define FUNC_NAME RegExpGlobalReplaceOpt
+#include "RegExpGlobalReplaceOpt.h.js"
+#undef FUNC_NAME
+
+// Conditions:
+// * global flag is true
+// * replaceValue is a function
+#define FUNC_NAME RegExpGlobalReplaceOptFunc
+#define FUNCTIONAL
+#include "RegExpGlobalReplaceOpt.h.js"
+#undef FUNCTIONAL
+#undef FUNC_NAME
+
+// Conditions:
+// * global flag is true
+// * replaceValue is a function that returns element of an object
+#define FUNC_NAME RegExpGlobalReplaceOptElemBase
+#define ELEMBASE
+#include "RegExpGlobalReplaceOpt.h.js"
+#undef ELEMBASE
+#undef FUNC_NAME
+
+// Conditions:
+// * global flag is true
+// * replaceValue is a string with "$"
+#define FUNC_NAME RegExpGlobalReplaceOptSubst
+#define SUBSTITUTION
+#include "RegExpGlobalReplaceOpt.h.js"
+#undef SUBSTITUTION
+#undef FUNC_NAME
+
+// Conditions:
+// * global flag is false
+// * replaceValue is a string without "$"
+#define FUNC_NAME RegExpLocalReplaceOpt
+#include "RegExpLocalReplaceOpt.h.js"
+#undef FUNC_NAME
+
+// Conditions:
+// * global flag is false
+// * replaceValue is a function
+#define FUNC_NAME RegExpLocalReplaceOptFunc
+#define FUNCTIONAL
+#include "RegExpLocalReplaceOpt.h.js"
+#undef FUNCTIONAL
+#undef FUNC_NAME
+
+// Conditions:
+// * global flag is false
+// * replaceValue is a string with "$"
+#define FUNC_NAME RegExpLocalReplaceOptSubst
+#define SUBSTITUTION
+#include "RegExpLocalReplaceOpt.h.js"
+#undef SUBSTITUTION
+#undef FUNC_NAME
+
+// ES 2017 draft 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.9.
+function RegExpSearch(string) {
+ // Step 1.
+ var rx = this;
+
+ // Step 2.
+ if (!IsObject(rx))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, rx === null ? "null" : typeof rx);
+
+ // Step 3.
+ var S = ToString(string);
+
+ if (IsRegExpMethodOptimizable(rx) && S.length < 0x7fff) {
+ // Step 6.
+ var result = RegExpSearcher(rx, S, 0);
+
+ // Step 8.
+ if (result === -1)
+ return -1;
+
+ // Step 9.
+ return result & 0x7fff;
+ }
+
+ return RegExpSearchSlowPath(rx, S);
+}
+
+// ES 2017 draft 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.9
+// steps 4-9.
+function RegExpSearchSlowPath(rx, S) {
+ // Step 4.
+ var previousLastIndex = rx.lastIndex;
+
+ // Step 5.
+ rx.lastIndex = 0;
+
+ // Step 6.
+ var result = RegExpExec(rx, S, false);
+
+ // Step 7.
+ rx.lastIndex = previousLastIndex;
+
+ // Step 8.
+ if (result === null)
+ return -1;
+
+ // Step 9.
+ return result.index;
+}
+
+function IsRegExpSplitOptimizable(rx, C) {
+ if (!IsRegExpObject(rx))
+ return false;
+
+ var RegExpCtor = GetBuiltinConstructor("RegExp");
+ if (C !== RegExpCtor)
+ return false;
+
+ var RegExpProto = RegExpCtor.prototype;
+ // If RegExpPrototypeOptimizable succeeds, `RegExpProto.exec` is guaranteed
+ // to be a data property.
+ return RegExpPrototypeOptimizable(RegExpProto) &&
+ RegExpInstanceOptimizable(rx, RegExpProto) &&
+ RegExpProto.exec === RegExp_prototype_Exec;
+}
+
+// ES 2017 draft 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.11.
+function RegExpSplit(string, limit) {
+ // Step 1.
+ var rx = this;
+
+ // Step 2.
+ if (!IsObject(rx))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, rx === null ? "null" : typeof rx);
+
+ // Step 3.
+ var S = ToString(string);
+
+ // Step 4.
+ var C = SpeciesConstructor(rx, GetBuiltinConstructor("RegExp"));
+
+ var optimizable = IsRegExpSplitOptimizable(rx, C) &&
+ (limit === undefined || typeof limit == "number");
+
+ var flags, unicodeMatching, splitter;
+ if (optimizable) {
+ // Step 5.
+ flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT);
+
+ // Steps 6-7.
+ unicodeMatching = !!(flags & (REGEXP_UNICODE_FLAG));
+
+ // Steps 8-10.
+ // If split operation is optimizable, perform non-sticky match.
+ splitter = regexp_construct_raw_flags(rx, flags & ~REGEXP_STICKY_FLAG);
+ } else {
+ // Step 5.
+ flags = ToString(rx.flags);
+
+ // Steps 6-7.
+ unicodeMatching = callFunction(std_String_includes, flags, "u");
+
+ // Steps 8-9.
+ var newFlags;
+ if (callFunction(std_String_includes, flags, "y"))
+ newFlags = flags;
+ else
+ newFlags = flags + "y";
+
+ // Step 10.
+ splitter = new C(rx, newFlags);
+ }
+
+ // Step 11.
+ var A = [];
+
+ // Step 12.
+ var lengthA = 0;
+
+ // Step 13.
+ var lim;
+ if (limit === undefined)
+ lim = MAX_NUMERIC_INDEX;
+ else
+ lim = limit >>> 0;
+
+ // Step 15.
+ var p = 0;
+
+ // Step 16.
+ if (lim === 0)
+ return A;
+
+ // Step 14 (reordered).
+ var size = S.length;
+
+ // Step 17.
+ if (size === 0) {
+ // Step 17.a.
+ var z;
+ if (optimizable)
+ z = RegExpMatcher(splitter, S, 0);
+ else
+ z = RegExpExec(splitter, S, false);
+
+ // Step 17.b.
+ if (z !== null)
+ return A;
+
+ // Step 17.d.
+ _DefineDataProperty(A, 0, S);
+
+ // Step 17.e.
+ return A;
+ }
+
+ // Step 18.
+ var q = p;
+
+ // Step 19.
+ while (q < size) {
+ var e;
+ if (optimizable) {
+ // Step 19.a (skipped).
+ // splitter.lastIndex is not used.
+
+ // Step 19.b.
+ z = RegExpMatcher(splitter, S, q);
+
+ // Step 19.c.
+ if (z === null)
+ break;
+
+ // splitter.lastIndex is not updated.
+ q = z.index;
+ if (q >= size)
+ break;
+
+ // Step 19.d.i.
+ e = q + z[0].length;
+ } else {
+ // Step 19.a.
+ splitter.lastIndex = q;
+
+ // Step 19.b.
+ z = RegExpExec(splitter, S, false);
+
+ // Step 19.c.
+ if (z === null) {
+ q = unicodeMatching ? AdvanceStringIndex(S, q) : q + 1;
+ continue;
+ }
+
+ // Step 19.d.i.
+ e = ToLength(splitter.lastIndex);
+ }
+
+ // Step 19.d.iii.
+ if (e === p) {
+ q = unicodeMatching ? AdvanceStringIndex(S, q) : q + 1;
+ continue;
+ }
+
+ // Steps 19.d.iv.1-3.
+ _DefineDataProperty(A, lengthA, Substring(S, p, q - p));
+
+ // Step 19.d.iv.4.
+ lengthA++;
+
+ // Step 19.d.iv.5.
+ if (lengthA === lim)
+ return A;
+
+ // Step 19.d.iv.6.
+ p = e;
+
+ // Steps 19.d.iv.7-8.
+ var numberOfCaptures = std_Math_max(ToLength(z.length) - 1, 0);
+
+ // Step 19.d.iv.9.
+ var i = 1;
+
+ // Step 19.d.iv.10.
+ while (i <= numberOfCaptures) {
+ // Steps 19.d.iv.10.a-b.
+ _DefineDataProperty(A, lengthA, z[i]);
+
+ // Step 19.d.iv.10.c.
+ i++;
+
+ // Step 19.d.iv.10.d.
+ lengthA++;
+
+ // Step 19.d.iv.10.e.
+ if (lengthA === lim)
+ return A;
+ }
+
+ // Step 19.d.iv.11.
+ q = p;
+ }
+
+ // Steps 20-22.
+ if (p >= size)
+ _DefineDataProperty(A, lengthA, "");
+ else
+ _DefineDataProperty(A, lengthA, Substring(S, p, size - p));
+
+ // Step 23.
+ return A;
+}
+
+// ES6 21.2.5.2.
+// NOTE: This is not RegExpExec (21.2.5.2.1).
+function RegExp_prototype_Exec(string) {
+ // Steps 1-3.
+ var R = this;
+ if (!IsObject(R) || !IsRegExpObject(R))
+ return callFunction(CallRegExpMethodIfWrapped, R, string, "RegExp_prototype_Exec");
+
+ // Steps 4-5.
+ var S = ToString(string);
+
+ // Step 6.
+ return RegExpBuiltinExec(R, S, false);
+}
+
+// ES6 21.2.5.2.1.
+function RegExpExec(R, S, forTest) {
+ // Steps 1-2 (skipped).
+
+ // Steps 3-4.
+ var exec = R.exec;
+
+ // Step 5.
+ // If exec is the original RegExp.prototype.exec, use the same, faster,
+ // path as for the case where exec isn't callable.
+ if (exec === RegExp_prototype_Exec || !IsCallable(exec)) {
+ // ES6 21.2.5.2 steps 1-2, 4-5 (skipped) for optimized case.
+
+ // Steps 6-7 or ES6 21.2.5.2 steps 3, 6 for optimized case.
+ return RegExpBuiltinExec(R, S, forTest);
+ }
+
+ // Steps 5.a-b.
+ var result = callContentFunction(exec, R, S);
+
+ // Step 5.c.
+ if (typeof result !== "object")
+ ThrowTypeError(JSMSG_EXEC_NOT_OBJORNULL);
+
+ // Step 5.d.
+ return forTest ? result !== null : result;
+}
+
+// ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2.
+function RegExpBuiltinExec(R, S, forTest) {
+ // ES6 21.2.5.2.1 step 6.
+ // This check is here for RegExpTest. RegExp_prototype_Exec does same
+ // thing already.
+ if (!IsRegExpObject(R))
+ return UnwrapAndCallRegExpBuiltinExec(R, S, forTest);
+
+ // Steps 1-2 (skipped).
+
+ // Step 4.
+ var lastIndex = ToLength(R.lastIndex);
+
+ // Step 5.
+ var flags = UnsafeGetInt32FromReservedSlot(R, REGEXP_FLAGS_SLOT);
+
+ // Steps 6-7.
+ var globalOrSticky = !!(flags & (REGEXP_GLOBAL_FLAG | REGEXP_STICKY_FLAG));
+
+ // Step 8.
+ if (!globalOrSticky) {
+ lastIndex = 0;
+ } else {
+ if (lastIndex > S.length) {
+ // Steps 12.a.i-ii, 12.c.i.1-2.
+ R.lastIndex = 0;
+ return forTest ? false : null;
+ }
+ }
+
+ if (forTest) {
+ // Steps 3, 9-25, except 12.a.i-ii, 12.c.i.1-2, 15.
+ var endIndex = RegExpTester(R, S, lastIndex);
+ if (endIndex == -1) {
+ // Steps 12.a.i-ii, 12.c.i.1-2.
+ R.lastIndex = 0;
+ return false;
+ }
+
+ // Step 15.
+ if (globalOrSticky)
+ R.lastIndex = endIndex;
+
+ return true;
+ }
+
+ // Steps 3, 9-25, except 12.a.i-ii, 12.c.i.1-2, 15.
+ var result = RegExpMatcher(R, S, lastIndex);
+ if (result === null) {
+ // Steps 12.a.i-ii, 12.c.i.1-2.
+ R.lastIndex = 0;
+ } else {
+ // Step 15.
+ if (globalOrSticky)
+ R.lastIndex = result.index + result[0].length;
+ }
+
+ return result;
+}
+
+function UnwrapAndCallRegExpBuiltinExec(R, S, forTest) {
+ return callFunction(CallRegExpMethodIfWrapped, R, S, forTest, "CallRegExpBuiltinExec");
+}
+
+function CallRegExpBuiltinExec(S, forTest) {
+ return RegExpBuiltinExec(this, S, forTest);
+}
+
+// ES6 21.2.5.13.
+function RegExpTest(string) {
+ // Steps 1-2.
+ var R = this;
+ if (!IsObject(R))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, R === null ? "null" : typeof R);
+
+ // Steps 3-4.
+ var S = ToString(string);
+
+ // Steps 5-6.
+ return RegExpExec(R, S, true);
+}
+
+// ES 2016 draft Mar 25, 2016 21.2.4.2.
+function RegExpSpecies() {
+ // Step 1.
+ return this;
+}
+_SetCanonicalName(RegExpSpecies, "get [Symbol.species]");
diff --git a/js/src/builtin/RegExpGlobalReplaceOpt.h.js b/js/src/builtin/RegExpGlobalReplaceOpt.h.js
new file mode 100644
index 000000000..fbe50a3f9
--- /dev/null
+++ b/js/src/builtin/RegExpGlobalReplaceOpt.h.js
@@ -0,0 +1,128 @@
+// Function template for the following functions:
+// * RegExpGlobalReplaceOpt
+// * RegExpGlobalReplaceOptFunc
+// * RegExpGlobalReplaceOptSubst
+// * RegExpGlobalReplaceOptElemBase
+// Define the following macro and include this file to declare function:
+// * FUNC_NAME -- function name (required)
+// e.g.
+// #define FUNC_NAME RegExpGlobalReplaceOpt
+// Define the following macro (without value) to switch the code:
+// * SUBSTITUTION -- replaceValue is a string with "$"
+// * FUNCTIONAL -- replaceValue is a function
+// * ELEMBASE -- replaceValue is a function that returns an element
+// of an object
+// * none of above -- replaceValue is a string without "$"
+
+// ES 2017 draft 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8
+// steps 8.b-16.
+// Optimized path for @@replace with the following conditions:
+// * global flag is true
+function FUNC_NAME(rx, S, lengthS, replaceValue, fullUnicode
+#ifdef SUBSTITUTION
+ , firstDollarIndex
+#endif
+#ifdef ELEMBASE
+ , elemBase
+#endif
+ )
+{
+ // Step 8.b.
+ var lastIndex = 0;
+ rx.lastIndex = 0;
+
+#if defined(FUNCTIONAL) || defined(SUBSTITUTION)
+ // Clone RegExp object here to avoid the effect of RegExp#compile,
+ // that may be called in replaceValue function.
+ rx = regexp_clone(rx);
+#endif
+
+ // Step 12 (reordered).
+ var accumulatedResult = "";
+
+ // Step 13 (reordered).
+ var nextSourcePosition = 0;
+
+ // Step 11.
+ while (true) {
+ // Step 11.a.
+ var result = RegExpMatcher(rx, S, lastIndex);
+
+ // Step 11.b.
+ if (result === null)
+ break;
+
+ var nCaptures;
+#if defined(FUNCTIONAL) || defined(SUBSTITUTION)
+ // Steps 14.a-b.
+ nCaptures = std_Math_max(result.length - 1, 0);
+#endif
+
+ // Step 14.c (reordered).
+ var matched = result[0];
+
+ // Step 14.d.
+ var matchLength = matched.length;
+
+ // Steps 14.e-f.
+ var position = result.index;
+ lastIndex = position + matchLength;
+
+ // Steps g-j.
+ var replacement;
+#if defined(FUNCTIONAL)
+ replacement = RegExpGetComplexReplacement(result, matched, S, position,
+
+ nCaptures, replaceValue,
+ true, -1);
+#elif defined(SUBSTITUTION)
+ replacement = RegExpGetComplexReplacement(result, matched, S, position,
+
+ nCaptures, replaceValue,
+ false, firstDollarIndex);
+#elif defined(ELEMBASE)
+ if (IsObject(elemBase)) {
+ var prop = GetStringDataProperty(elemBase, matched);
+ if (prop !== undefined) {
+ assert(typeof prop === "string", "GetStringDataProperty should return either string or undefined");
+ replacement = prop;
+ } else {
+ elemBase = undefined;
+ }
+ }
+
+ if (!IsObject(elemBase)) {
+ // Steps 14.a-b (reordered).
+ nCaptures = std_Math_max(result.length - 1, 0);
+
+ replacement = RegExpGetComplexReplacement(result, matched, S, position,
+
+ nCaptures, replaceValue,
+ true, -1);
+ }
+#else
+ replacement = replaceValue;
+#endif
+
+ // Step 14.l.ii.
+ accumulatedResult += Substring(S, nextSourcePosition,
+ position - nextSourcePosition) + replacement;
+
+ // Step 14.l.iii.
+ nextSourcePosition = lastIndex;
+
+ // Step 11.c.iii.2.
+ if (matchLength === 0) {
+ lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1;
+ if (lastIndex > lengthS)
+ break;
+ }
+ }
+
+ // Step 15.
+ if (nextSourcePosition >= lengthS)
+ return accumulatedResult;
+
+ // Step 16.
+ return accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition);
+}
diff --git a/js/src/builtin/RegExpLocalReplaceOpt.h.js b/js/src/builtin/RegExpLocalReplaceOpt.h.js
new file mode 100644
index 000000000..edc2e2056
--- /dev/null
+++ b/js/src/builtin/RegExpLocalReplaceOpt.h.js
@@ -0,0 +1,92 @@
+// Function template for the following functions:
+// * RegExpLocalReplaceOpt
+// * RegExpLocalReplaceOptFunc
+// * RegExpLocalReplaceOptSubst
+// Define the following macro and include this file to declare function:
+// * FUNC_NAME -- function name (required)
+// e.g.
+// #define FUNC_NAME RegExpLocalReplaceOpt
+// Define the following macro (without value) to switch the code:
+// * SUBSTITUTION -- replaceValue is a string with "$"
+// * FUNCTIONAL -- replaceValue is a function
+// * neither of above -- replaceValue is a string without "$"
+
+// ES 2017 draft 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8
+// steps 11.a-16.
+// Optimized path for @@replace with the following conditions:
+// * global flag is false
+function FUNC_NAME(rx, S, lengthS, replaceValue, sticky
+#ifdef SUBSTITUTION
+ , firstDollarIndex
+#endif
+ )
+{
+ var lastIndex;
+ if (sticky) {
+ lastIndex = ToLength(rx.lastIndex);
+ if (lastIndex > lengthS) {
+ rx.lastIndex = 0;
+ return S;
+ }
+ } else {
+ lastIndex = 0;
+ }
+
+ // Step 11.a.
+ var result = RegExpMatcher(rx, S, lastIndex);
+
+ // Step 11.b.
+ if (result === null) {
+ rx.lastIndex = 0;
+ return S;
+ }
+
+ // Steps 11.c, 12-13, 14.a-b (skipped).
+
+#if defined(FUNCTIONAL) || defined(SUBSTITUTION)
+ // Steps 14.a-b.
+ var nCaptures = std_Math_max(result.length - 1, 0);
+#endif
+
+ // Step 14.c.
+ var matched = result[0];
+
+ // Step 14.d.
+ var matchLength = matched.length;
+
+ // Step 14.e-f.
+ var position = result.index;
+
+ // Step 14.l.iii (reordered)
+ // To set rx.lastIndex before RegExpGetComplexReplacement.
+ var nextSourcePosition = position + matchLength;
+
+ if (sticky)
+ rx.lastIndex = nextSourcePosition;
+
+ var replacement;
+ // Steps g-j.
+#if defined(FUNCTIONAL)
+ replacement = RegExpGetComplexReplacement(result, matched, S, position,
+
+ nCaptures, replaceValue,
+ true, -1);
+#elif defined(SUBSTITUTION)
+ replacement = RegExpGetComplexReplacement(result, matched, S, position,
+
+ nCaptures, replaceValue,
+ false, firstDollarIndex);
+#else
+ replacement = replaceValue;
+#endif
+
+ // Step 14.l.ii.
+ var accumulatedResult = Substring(S, 0, position) + replacement;
+
+ // Step 15.
+ if (nextSourcePosition >= lengthS)
+ return accumulatedResult;
+
+ // Step 16.
+ return accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition);
+}
diff --git a/js/src/builtin/SIMD.cpp b/js/src/builtin/SIMD.cpp
new file mode 100644
index 000000000..2383922db
--- /dev/null
+++ b/js/src/builtin/SIMD.cpp
@@ -0,0 +1,1552 @@
+/* -*- 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/. */
+
+/*
+ * JS SIMD pseudo-module.
+ * Specification matches polyfill:
+ * https://github.com/johnmccutchan/ecmascript_simd/blob/master/src/ecmascript_simd.js
+ * The objects float32x4 and int32x4 are installed on the SIMD pseudo-module.
+ */
+
+#include "builtin/SIMD.h"
+
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/IntegerTypeTraits.h"
+#include "mozilla/Sprintf.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "jsnum.h"
+#include "jsprf.h"
+
+#include "builtin/TypedObject.h"
+#include "jit/InlinableNatives.h"
+#include "js/GCAPI.h"
+#include "js/Value.h"
+
+#include "jsobjinlines.h"
+
+using namespace js;
+
+using mozilla::ArrayLength;
+using mozilla::IsFinite;
+using mozilla::IsNaN;
+using mozilla::FloorLog2;
+using mozilla::NumberIsInt32;
+
+///////////////////////////////////////////////////////////////////////////
+// SIMD
+
+static_assert(unsigned(SimdType::Count) == 12, "sync with TypedObjectConstants.h");
+
+static bool ArgumentToLaneIndex(JSContext* cx, JS::HandleValue v, unsigned limit, unsigned* lane);
+
+static bool
+CheckVectorObject(HandleValue v, SimdType expectedType)
+{
+ if (!v.isObject())
+ return false;
+
+ JSObject& obj = v.toObject();
+ if (!obj.is<TypedObject>())
+ return false;
+
+ TypeDescr& typeRepr = obj.as<TypedObject>().typeDescr();
+ if (typeRepr.kind() != type::Simd)
+ return false;
+
+ return typeRepr.as<SimdTypeDescr>().type() == expectedType;
+}
+
+template<class V>
+bool
+js::IsVectorObject(HandleValue v)
+{
+ return CheckVectorObject(v, V::type);
+}
+
+#define FOR_EACH_SIMD(macro) \
+ macro(Int8x16) \
+ macro(Int16x8) \
+ macro(Int32x4) \
+ macro(Uint8x16) \
+ macro(Uint16x8) \
+ macro(Uint32x4) \
+ macro(Float32x4) \
+ macro(Float64x2) \
+ macro(Bool8x16) \
+ macro(Bool16x8) \
+ macro(Bool32x4) \
+ macro(Bool64x2)
+
+#define InstantiateIsVectorObject_(T) \
+ template bool js::IsVectorObject<T>(HandleValue v);
+FOR_EACH_SIMD(InstantiateIsVectorObject_)
+#undef InstantiateIsVectorObject_
+
+const char*
+js::SimdTypeToString(SimdType type)
+{
+ switch (type) {
+#define RETSTR_(TYPE) case SimdType::TYPE: return #TYPE;
+ FOR_EACH_SIMD(RETSTR_)
+#undef RETSTR_
+ case SimdType::Count: break;
+ }
+ return "<bad SimdType>";
+}
+
+PropertyName*
+js::SimdTypeToName(const JSAtomState& atoms, SimdType type)
+{
+ switch (type) {
+#define CASE_(TypeName) case SimdType::TypeName: return atoms.TypeName;
+ FOR_EACH_SIMD(CASE_)
+#undef CASE_
+ case SimdType::Count: break;
+ }
+ MOZ_CRASH("bad SIMD type");
+}
+
+bool
+js::IsSimdTypeName(const JSAtomState& atoms, const PropertyName* name, SimdType* type)
+{
+#define CHECK_(TypeName) if (name == atoms.TypeName) { \
+ *type = SimdType::TypeName; \
+ return true; \
+ }
+ FOR_EACH_SIMD(CHECK_)
+#undef CHECK_
+ return false;
+}
+
+static inline bool
+ErrorBadArgs(JSContext* cx)
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
+ return false;
+}
+
+static inline bool
+ErrorWrongTypeArg(JSContext* cx, unsigned argIndex, Handle<TypeDescr*> typeDescr)
+{
+ MOZ_ASSERT(argIndex < 10);
+ char charArgIndex[2];
+ SprintfLiteral(charArgIndex, "%u", argIndex);
+
+ HeapSlot& typeNameSlot = typeDescr->getReservedSlotRef(JS_DESCR_SLOT_STRING_REPR);
+ char* typeNameStr = JS_EncodeString(cx, typeNameSlot.toString());
+ if (!typeNameStr)
+ return false;
+
+ JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_SIMD_NOT_A_VECTOR,
+ typeNameStr, charArgIndex);
+ JS_free(cx, typeNameStr);
+ return false;
+}
+
+static inline bool
+ErrorBadIndex(JSContext* cx)
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
+ return false;
+}
+
+template<typename T>
+static SimdTypeDescr*
+GetTypeDescr(JSContext* cx)
+{
+ RootedGlobalObject global(cx, cx->global());
+ return GlobalObject::getOrCreateSimdTypeDescr(cx, global, T::type);
+}
+
+template<typename V>
+bool
+js::ToSimdConstant(JSContext* cx, HandleValue v, jit::SimdConstant* out)
+{
+ typedef typename V::Elem Elem;
+ Rooted<TypeDescr*> typeDescr(cx, GetTypeDescr<V>(cx));
+ if (!typeDescr)
+ return false;
+ if (!IsVectorObject<V>(v))
+ return ErrorWrongTypeArg(cx, 1, typeDescr);
+
+ JS::AutoCheckCannotGC nogc(cx);
+ Elem* mem = reinterpret_cast<Elem*>(v.toObject().as<TypedObject>().typedMem(nogc));
+ *out = jit::SimdConstant::CreateSimd128(mem);
+ return true;
+}
+
+template bool js::ToSimdConstant<Int8x16>(JSContext* cx, HandleValue v, jit::SimdConstant* out);
+template bool js::ToSimdConstant<Int16x8>(JSContext* cx, HandleValue v, jit::SimdConstant* out);
+template bool js::ToSimdConstant<Int32x4>(JSContext* cx, HandleValue v, jit::SimdConstant* out);
+template bool js::ToSimdConstant<Float32x4>(JSContext* cx, HandleValue v, jit::SimdConstant* out);
+template bool js::ToSimdConstant<Bool8x16>(JSContext* cx, HandleValue v, jit::SimdConstant* out);
+template bool js::ToSimdConstant<Bool16x8>(JSContext* cx, HandleValue v, jit::SimdConstant* out);
+template bool js::ToSimdConstant<Bool32x4>(JSContext* cx, HandleValue v, jit::SimdConstant* out);
+
+template<typename Elem>
+static Elem
+TypedObjectMemory(HandleValue v, const JS::AutoRequireNoGC& nogc)
+{
+ TypedObject& obj = v.toObject().as<TypedObject>();
+ return reinterpret_cast<Elem>(obj.typedMem(nogc));
+}
+
+static const ClassOps SimdTypeDescrClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ TypeDescr::finalize,
+ SimdTypeDescr::call
+};
+
+const Class SimdTypeDescr::class_ = {
+ "SIMD",
+ JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
+ &SimdTypeDescrClassOps
+};
+
+namespace {
+
+// Define classes (Int8x16Defn, Int16x8Defn, etc.) to group together various
+// properties and so on.
+#define DEFINE_DEFN_(TypeName) \
+class TypeName##Defn { \
+ public: \
+ static const JSFunctionSpec Methods[]; \
+};
+
+FOR_EACH_SIMD(DEFINE_DEFN_)
+#undef DEFINE_DEFN_
+
+} // namespace
+
+// Shared type descriptor methods for all SIMD types.
+static const JSFunctionSpec TypeDescriptorMethods[] = {
+ JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
+ JS_SELF_HOSTED_FN("array", "ArrayShorthand", 1, 0),
+ JS_SELF_HOSTED_FN("equivalent", "TypeDescrEquivalent", 1, 0),
+ JS_FS_END
+};
+
+// Shared TypedObject methods for all SIMD types.
+static const JSFunctionSpec SimdTypedObjectMethods[] = {
+ JS_SELF_HOSTED_FN("toString", "SimdToString", 0, 0),
+ JS_SELF_HOSTED_FN("valueOf", "SimdValueOf", 0, 0),
+ JS_SELF_HOSTED_FN("toSource", "SimdToSource", 0, 0),
+ JS_FS_END
+};
+
+// Provide JSJitInfo structs for those types that are supported by Ion.
+// The controlling SIMD type is encoded as the InlinableNative primary opcode.
+// The SimdOperation within the type is encoded in the .depth field.
+//
+// The JS_INLINABLE_FN macro refers to js::JitInfo_##native which we provide as
+// Simd##Type##_##Operation
+//
+// /!\ Don't forget to keep this list in sync with the SIMD instrinics used in
+// SelfHosting.cpp.
+
+namespace js {
+namespace jit {
+
+static_assert(uint64_t(SimdOperation::Last) <= UINT16_MAX, "SimdOperation must fit in uint16_t");
+
+// See also JitInfo_* in MCallOptimize.cpp. We provide a JSJitInfo for all the
+// named functions here. The default JitInfo_SimdInt32x4 etc structs represent the
+// SimdOperation::Constructor.
+#define DEFN(TYPE, OP) const JSJitInfo JitInfo_Simd##TYPE##_##OP = { \
+ /* .getter, unused for inlinable natives. */ \
+ { nullptr }, \
+ /* .inlinableNative, but we have to init first union member: .protoID. */ \
+ { uint16_t(InlinableNative::Simd##TYPE) }, \
+ /* .nativeOp. Actually initializing first union member .depth. */ \
+ { uint16_t(SimdOperation::Fn_##OP) }, \
+ /* .type_ bitfield says this in an inlinable native function. */ \
+ JSJitInfo::InlinableNative \
+ /* Remaining fields are not used for inlinable natives. They are zero-initialized. */ \
+};
+
+// This list of inlinable types should match the one in jit/InlinableNatives.h.
+#define TDEFN(Name, Func, Operands) DEFN(Float32x4, Name)
+FLOAT32X4_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
+#define TDEFN(Name, Func, Operands) DEFN(Int8x16, Name)
+INT8X16_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
+#define TDEFN(Name, Func, Operands) DEFN(Uint8x16, Name)
+UINT8X16_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
+#define TDEFN(Name, Func, Operands) DEFN(Int16x8, Name)
+INT16X8_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
+#define TDEFN(Name, Func, Operands) DEFN(Uint16x8, Name)
+UINT16X8_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
+#define TDEFN(Name, Func, Operands) DEFN(Int32x4, Name)
+INT32X4_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
+#define TDEFN(Name, Func, Operands) DEFN(Uint32x4, Name)
+UINT32X4_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
+#define TDEFN(Name, Func, Operands) DEFN(Bool8x16, Name)
+BOOL8X16_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
+#define TDEFN(Name, Func, Operands) DEFN(Bool16x8, Name)
+BOOL16X8_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
+#define TDEFN(Name, Func, Operands) DEFN(Bool32x4, Name)
+BOOL32X4_FUNCTION_LIST(TDEFN)
+#undef TDEFN
+
+} // namespace jit
+} // namespace js
+
+const JSFunctionSpec Float32x4Defn::Methods[] = {
+#define SIMD_FLOAT32X4_FUNCTION_ITEM(Name, Func, Operands) \
+ JS_INLINABLE_FN(#Name, js::simd_float32x4_##Name, Operands, 0, SimdFloat32x4_##Name),
+ FLOAT32X4_FUNCTION_LIST(SIMD_FLOAT32X4_FUNCTION_ITEM)
+#undef SIMD_FLOAT32x4_FUNCTION_ITEM
+ JS_FS_END
+};
+
+const JSFunctionSpec Float64x2Defn::Methods[] = {
+#define SIMD_FLOAT64X2_FUNCTION_ITEM(Name, Func, Operands) \
+ JS_FN(#Name, js::simd_float64x2_##Name, Operands, 0),
+ FLOAT64X2_FUNCTION_LIST(SIMD_FLOAT64X2_FUNCTION_ITEM)
+#undef SIMD_FLOAT64X2_FUNCTION_ITEM
+ JS_FS_END
+};
+
+const JSFunctionSpec Int8x16Defn::Methods[] = {
+#define SIMD_INT8X16_FUNCTION_ITEM(Name, Func, Operands) \
+ JS_INLINABLE_FN(#Name, js::simd_int8x16_##Name, Operands, 0, SimdInt8x16_##Name),
+ INT8X16_FUNCTION_LIST(SIMD_INT8X16_FUNCTION_ITEM)
+#undef SIMD_INT8X16_FUNCTION_ITEM
+ JS_FS_END
+};
+
+const JSFunctionSpec Int16x8Defn::Methods[] = {
+#define SIMD_INT16X8_FUNCTION_ITEM(Name, Func, Operands) \
+ JS_INLINABLE_FN(#Name, js::simd_int16x8_##Name, Operands, 0, SimdInt16x8_##Name),
+ INT16X8_FUNCTION_LIST(SIMD_INT16X8_FUNCTION_ITEM)
+#undef SIMD_INT16X8_FUNCTION_ITEM
+ JS_FS_END
+};
+
+const JSFunctionSpec Int32x4Defn::Methods[] = {
+#define SIMD_INT32X4_FUNCTION_ITEM(Name, Func, Operands) \
+ JS_INLINABLE_FN(#Name, js::simd_int32x4_##Name, Operands, 0, SimdInt32x4_##Name),
+ INT32X4_FUNCTION_LIST(SIMD_INT32X4_FUNCTION_ITEM)
+#undef SIMD_INT32X4_FUNCTION_ITEM
+ JS_FS_END
+};
+
+const JSFunctionSpec Uint8x16Defn::Methods[] = {
+#define SIMD_UINT8X16_FUNCTION_ITEM(Name, Func, Operands) \
+ JS_INLINABLE_FN(#Name, js::simd_uint8x16_##Name, Operands, 0, SimdUint8x16_##Name),
+ UINT8X16_FUNCTION_LIST(SIMD_UINT8X16_FUNCTION_ITEM)
+#undef SIMD_UINT8X16_FUNCTION_ITEM
+ JS_FS_END
+};
+
+const JSFunctionSpec Uint16x8Defn::Methods[] = {
+#define SIMD_UINT16X8_FUNCTION_ITEM(Name, Func, Operands) \
+ JS_INLINABLE_FN(#Name, js::simd_uint16x8_##Name, Operands, 0, SimdUint16x8_##Name),
+ UINT16X8_FUNCTION_LIST(SIMD_UINT16X8_FUNCTION_ITEM)
+#undef SIMD_UINT16X8_FUNCTION_ITEM
+ JS_FS_END
+};
+
+const JSFunctionSpec Uint32x4Defn::Methods[] = {
+#define SIMD_UINT32X4_FUNCTION_ITEM(Name, Func, Operands) \
+ JS_INLINABLE_FN(#Name, js::simd_uint32x4_##Name, Operands, 0, SimdUint32x4_##Name),
+ UINT32X4_FUNCTION_LIST(SIMD_UINT32X4_FUNCTION_ITEM)
+#undef SIMD_UINT32X4_FUNCTION_ITEM
+ JS_FS_END
+};
+
+const JSFunctionSpec Bool8x16Defn::Methods[] = {
+#define SIMD_BOOL8X16_FUNCTION_ITEM(Name, Func, Operands) \
+ JS_INLINABLE_FN(#Name, js::simd_bool8x16_##Name, Operands, 0, SimdBool8x16_##Name),
+ BOOL8X16_FUNCTION_LIST(SIMD_BOOL8X16_FUNCTION_ITEM)
+#undef SIMD_BOOL8X16_FUNCTION_ITEM
+ JS_FS_END
+};
+
+const JSFunctionSpec Bool16x8Defn::Methods[] = {
+#define SIMD_BOOL16X8_FUNCTION_ITEM(Name, Func, Operands) \
+ JS_INLINABLE_FN(#Name, js::simd_bool16x8_##Name, Operands, 0, SimdBool16x8_##Name),
+ BOOL16X8_FUNCTION_LIST(SIMD_BOOL16X8_FUNCTION_ITEM)
+#undef SIMD_BOOL16X8_FUNCTION_ITEM
+ JS_FS_END
+};
+
+const JSFunctionSpec Bool32x4Defn::Methods[] = {
+#define SIMD_BOOL32X4_FUNCTION_ITEM(Name, Func, Operands) \
+ JS_INLINABLE_FN(#Name, js::simd_bool32x4_##Name, Operands, 0, SimdBool32x4_##Name),
+ BOOL32X4_FUNCTION_LIST(SIMD_BOOL32X4_FUNCTION_ITEM)
+#undef SIMD_BOOL32X4_FUNCTION_ITEM
+ JS_FS_END
+};
+
+const JSFunctionSpec Bool64x2Defn::Methods[] = {
+#define SIMD_BOOL64X2_FUNCTION_ITEM(Name, Func, Operands) \
+ JS_FN(#Name, js::simd_bool64x2_##Name, Operands, 0),
+ BOOL64X2_FUNCTION_LIST(SIMD_BOOL64X2_FUNCTION_ITEM)
+#undef SIMD_BOOL64x2_FUNCTION_ITEM
+ JS_FS_END
+};
+
+template <typename T>
+static bool
+FillLanes(JSContext* cx, Handle<TypedObject*> result, const CallArgs& args)
+{
+ typedef typename T::Elem Elem;
+ Elem tmp;
+ for (unsigned i = 0; i < T::lanes; i++) {
+ if (!T::Cast(cx, args.get(i), &tmp))
+ return false;
+ // Reassure typedMem() that we won't GC while holding onto the returned
+ // pointer, even though we could GC on every iteration of this loop
+ // (but it is safe because we re-fetch each time.)
+ JS::AutoCheckCannotGC nogc(cx);
+ reinterpret_cast<Elem*>(result->typedMem(nogc))[i] = tmp;
+ }
+ args.rval().setObject(*result);
+ return true;
+}
+
+bool
+SimdTypeDescr::call(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<SimdTypeDescr*> descr(cx, &args.callee().as<SimdTypeDescr>());
+ Rooted<TypedObject*> result(cx, TypedObject::createZeroed(cx, descr, 0));
+ if (!result)
+ return false;
+
+#define CASE_CALL_(Type) \
+ case SimdType::Type: return FillLanes< ::Type>(cx, result, args);
+
+ switch (descr->type()) {
+ FOR_EACH_SIMD(CASE_CALL_)
+ case SimdType::Count: break;
+ }
+
+#undef CASE_CALL_
+ MOZ_CRASH("unexpected SIMD descriptor");
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// SIMD class
+
+static const ClassOps SimdObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ SimdObject::resolve
+};
+
+const Class SimdObject::class_ = {
+ "SIMD",
+ JSCLASS_HAS_RESERVED_SLOTS(uint32_t(SimdType::Count)),
+ &SimdObjectClassOps
+};
+
+bool
+GlobalObject::initSimdObject(JSContext* cx, Handle<GlobalObject*> global)
+{
+ // SIMD relies on the TypedObject module being initialized.
+ // In particular, the self-hosted code for array() wants
+ // to be able to call GetTypedObjectModule(). It is NOT necessary
+ // to install the TypedObjectModule global, but at the moment
+ // those two things are not separable.
+ if (!global->getOrCreateTypedObjectModule(cx))
+ return false;
+
+ RootedObject globalSimdObject(cx);
+ RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
+ if (!objProto)
+ return false;
+
+ globalSimdObject = NewObjectWithGivenProto(cx, &SimdObject::class_, objProto, SingletonObject);
+ if (!globalSimdObject)
+ return false;
+
+ RootedValue globalSimdValue(cx, ObjectValue(*globalSimdObject));
+ if (!DefineProperty(cx, global, cx->names().SIMD, globalSimdValue, nullptr, nullptr,
+ JSPROP_RESOLVING))
+ {
+ return false;
+ }
+
+ global->setConstructor(JSProto_SIMD, globalSimdValue);
+ return true;
+}
+
+static bool
+CreateSimdType(JSContext* cx, Handle<GlobalObject*> global, HandlePropertyName stringRepr,
+ SimdType simdType, const JSFunctionSpec* methods)
+{
+ RootedObject funcProto(cx, global->getOrCreateFunctionPrototype(cx));
+ if (!funcProto)
+ return false;
+
+ // Create type constructor itself and initialize its reserved slots.
+ Rooted<SimdTypeDescr*> typeDescr(cx);
+ typeDescr = NewObjectWithGivenProto<SimdTypeDescr>(cx, funcProto, SingletonObject);
+ if (!typeDescr)
+ return false;
+
+ typeDescr->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(type::Simd));
+ typeDescr->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(stringRepr));
+ typeDescr->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(SimdTypeDescr::alignment(simdType)));
+ typeDescr->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(SimdTypeDescr::size(simdType)));
+ typeDescr->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(false));
+ typeDescr->initReservedSlot(JS_DESCR_SLOT_TYPE, Int32Value(uint8_t(simdType)));
+
+ if (!CreateUserSizeAndAlignmentProperties(cx, typeDescr))
+ return false;
+
+ // Create prototype property, which inherits from Object.prototype.
+ RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
+ if (!objProto)
+ return false;
+ Rooted<TypedProto*> proto(cx);
+ proto = NewObjectWithGivenProto<TypedProto>(cx, objProto, SingletonObject);
+ if (!proto)
+ return false;
+ typeDescr->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*proto));
+
+ // Link constructor to prototype and install properties.
+ if (!JS_DefineFunctions(cx, typeDescr, TypeDescriptorMethods))
+ return false;
+
+ if (!LinkConstructorAndPrototype(cx, typeDescr, proto) ||
+ !JS_DefineFunctions(cx, proto, SimdTypedObjectMethods))
+ {
+ return false;
+ }
+
+ // Bind type descriptor to the global SIMD object
+ RootedObject globalSimdObject(cx, global->getOrCreateSimdGlobalObject(cx));
+ MOZ_ASSERT(globalSimdObject);
+
+ RootedValue typeValue(cx, ObjectValue(*typeDescr));
+ if (!JS_DefineFunctions(cx, typeDescr, methods) ||
+ !DefineProperty(cx, globalSimdObject, stringRepr, typeValue, nullptr, nullptr,
+ JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_RESOLVING))
+ {
+ return false;
+ }
+
+ uint32_t slot = uint32_t(typeDescr->type());
+ MOZ_ASSERT(globalSimdObject->as<NativeObject>().getReservedSlot(slot).isUndefined());
+ globalSimdObject->as<NativeObject>().setReservedSlot(slot, ObjectValue(*typeDescr));
+ return !!typeDescr;
+}
+
+bool
+GlobalObject::initSimdType(JSContext* cx, Handle<GlobalObject*> global, SimdType simdType)
+{
+#define CREATE_(Type) \
+ case SimdType::Type: \
+ return CreateSimdType(cx, global, cx->names().Type, simdType, Type##Defn::Methods);
+
+ switch (simdType) {
+ FOR_EACH_SIMD(CREATE_)
+ case SimdType::Count: break;
+ }
+ MOZ_CRASH("unexpected simd type");
+
+#undef CREATE_
+}
+
+SimdTypeDescr*
+GlobalObject::getOrCreateSimdTypeDescr(JSContext* cx, Handle<GlobalObject*> global,
+ SimdType simdType)
+{
+ MOZ_ASSERT(unsigned(simdType) < unsigned(SimdType::Count), "Invalid SIMD type");
+
+ RootedObject globalSimdObject(cx, global->getOrCreateSimdGlobalObject(cx));
+ if (!globalSimdObject)
+ return nullptr;
+
+ uint32_t typeSlotIndex = uint32_t(simdType);
+ if (globalSimdObject->as<NativeObject>().getReservedSlot(typeSlotIndex).isUndefined() &&
+ !GlobalObject::initSimdType(cx, global, simdType))
+ {
+ return nullptr;
+ }
+
+ const Value& slot = globalSimdObject->as<NativeObject>().getReservedSlot(typeSlotIndex);
+ MOZ_ASSERT(slot.isObject());
+ return &slot.toObject().as<SimdTypeDescr>();
+}
+
+bool
+SimdObject::resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved)
+{
+ *resolved = false;
+ if (!JSID_IS_ATOM(id))
+ return true;
+ JSAtom* str = JSID_TO_ATOM(id);
+ Rooted<GlobalObject*> global(cx, cx->global());
+#define TRY_RESOLVE_(Type) \
+ if (str == cx->names().Type) { \
+ *resolved = CreateSimdType(cx, global, cx->names().Type, \
+ SimdType::Type, Type##Defn::Methods); \
+ return *resolved; \
+ }
+ FOR_EACH_SIMD(TRY_RESOLVE_)
+#undef TRY_RESOLVE_
+ return true;
+}
+
+JSObject*
+js::InitSimdClass(JSContext* cx, HandleObject obj)
+{
+ Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+ return global->getOrCreateSimdGlobalObject(cx);
+}
+
+template<typename V>
+JSObject*
+js::CreateSimd(JSContext* cx, const typename V::Elem* data)
+{
+ typedef typename V::Elem Elem;
+ Rooted<TypeDescr*> typeDescr(cx, GetTypeDescr<V>(cx));
+ if (!typeDescr)
+ return nullptr;
+
+ Rooted<TypedObject*> result(cx, TypedObject::createZeroed(cx, typeDescr, 0));
+ if (!result)
+ return nullptr;
+
+ JS::AutoCheckCannotGC nogc(cx);
+ Elem* resultMem = reinterpret_cast<Elem*>(result->typedMem(nogc));
+ memcpy(resultMem, data, sizeof(Elem) * V::lanes);
+ return result;
+}
+
+#define InstantiateCreateSimd_(Type) \
+ template JSObject* js::CreateSimd<Type>(JSContext* cx, const Type::Elem* data);
+
+FOR_EACH_SIMD(InstantiateCreateSimd_)
+
+#undef InstantiateCreateSimd_
+
+#undef FOR_EACH_SIMD
+
+namespace js {
+// Unary SIMD operators
+template<typename T>
+struct Identity {
+ static T apply(T x) { return x; }
+};
+template<typename T>
+struct Abs {
+ static T apply(T x) { return mozilla::Abs(x); }
+};
+template<typename T>
+struct Neg {
+ static T apply(T x) { return -1 * x; }
+};
+template<typename T>
+struct Not {
+ static T apply(T x) { return ~x; }
+};
+template<typename T>
+struct LogicalNot {
+ static T apply(T x) { return !x; }
+};
+template<typename T>
+struct RecApprox {
+ static T apply(T x) { return 1 / x; }
+};
+template<typename T>
+struct RecSqrtApprox {
+ static T apply(T x) { return 1 / sqrt(x); }
+};
+template<typename T>
+struct Sqrt {
+ static T apply(T x) { return sqrt(x); }
+};
+
+// Binary SIMD operators
+template<typename T>
+struct Add {
+ static T apply(T l, T r) { return l + r; }
+};
+template<typename T>
+struct Sub {
+ static T apply(T l, T r) { return l - r; }
+};
+template<typename T>
+struct Div {
+ static T apply(T l, T r) { return l / r; }
+};
+template<typename T>
+struct Mul {
+ static T apply(T l, T r) { return l * r; }
+};
+template<typename T>
+struct Minimum {
+ static T apply(T l, T r) { return math_min_impl(l, r); }
+};
+template<typename T>
+struct MinNum {
+ static T apply(T l, T r) { return IsNaN(l) ? r : (IsNaN(r) ? l : math_min_impl(l, r)); }
+};
+template<typename T>
+struct Maximum {
+ static T apply(T l, T r) { return math_max_impl(l, r); }
+};
+template<typename T>
+struct MaxNum {
+ static T apply(T l, T r) { return IsNaN(l) ? r : (IsNaN(r) ? l : math_max_impl(l, r)); }
+};
+template<typename T>
+struct LessThan {
+ static bool apply(T l, T r) { return l < r; }
+};
+template<typename T>
+struct LessThanOrEqual {
+ static bool apply(T l, T r) { return l <= r; }
+};
+template<typename T>
+struct GreaterThan {
+ static bool apply(T l, T r) { return l > r; }
+};
+template<typename T>
+struct GreaterThanOrEqual {
+ static bool apply(T l, T r) { return l >= r; }
+};
+template<typename T>
+struct Equal {
+ static bool apply(T l, T r) { return l == r; }
+};
+template<typename T>
+struct NotEqual {
+ static bool apply(T l, T r) { return l != r; }
+};
+template<typename T>
+struct Xor {
+ static T apply(T l, T r) { return l ^ r; }
+};
+template<typename T>
+struct And {
+ static T apply(T l, T r) { return l & r; }
+};
+template<typename T>
+struct Or {
+ static T apply(T l, T r) { return l | r; }
+};
+
+// For the following three operators, if the value v we're trying to shift is
+// such that v << bits can't fit in the int32 range, then we have undefined
+// behavior, according to C++11 [expr.shift]p2. However, left-shifting an
+// unsigned type is well-defined.
+//
+// In C++, shifting by an amount outside the range [0;N-1] is undefined
+// behavior. SIMD.js reduces the shift amount modulo the number of bits in a
+// lane and has defined behavior for all shift amounts.
+template<typename T>
+struct ShiftLeft {
+ static T apply(T v, int32_t bits) {
+ typedef typename mozilla::MakeUnsigned<T>::Type UnsignedT;
+ uint32_t maskedBits = uint32_t(bits) % (sizeof(T) * 8);
+ return UnsignedT(v) << maskedBits;
+ }
+};
+template<typename T>
+struct ShiftRightArithmetic {
+ static T apply(T v, int32_t bits) {
+ typedef typename mozilla::MakeSigned<T>::Type SignedT;
+ uint32_t maskedBits = uint32_t(bits) % (sizeof(T) * 8);
+ return SignedT(v) >> maskedBits;
+ }
+};
+template<typename T>
+struct ShiftRightLogical {
+ static T apply(T v, int32_t bits) {
+ typedef typename mozilla::MakeUnsigned<T>::Type UnsignedT;
+ uint32_t maskedBits = uint32_t(bits) % (sizeof(T) * 8);
+ return UnsignedT(v) >> maskedBits;
+ }
+};
+
+// Saturating arithmetic is only defined on types smaller than int.
+// Clamp `x` into the range supported by the integral type T.
+template<typename T>
+static T
+Saturate(int x)
+{
+ static_assert(mozilla::IsIntegral<T>::value, "Only integer saturation supported");
+ static_assert(sizeof(T) < sizeof(int), "Saturating int-sized arithmetic is not safe");
+ const T lower = mozilla::MinValue<T>::value;
+ const T upper = mozilla::MaxValue<T>::value;
+ if (x > int(upper))
+ return upper;
+ if (x < int(lower))
+ return lower;
+ return T(x);
+}
+
+// Since signed integer overflow is undefined behavior in C++, it would be
+// wildly irresponsible to attempt something as dangerous as adding two numbers
+// coming from user code. However, in this case we know that T is smaller than
+// int, so there is no way these operations can cause overflow. The
+// static_assert in Saturate() enforces this for us.
+template<typename T>
+struct AddSaturate {
+ static T apply(T l, T r) { return Saturate<T>(l + r); }
+};
+template<typename T>
+struct SubSaturate {
+ static T apply(T l, T r) { return Saturate<T>(l - r); }
+};
+
+} // namespace js
+
+template<typename Out>
+static bool
+StoreResult(JSContext* cx, CallArgs& args, typename Out::Elem* result)
+{
+ RootedObject obj(cx, CreateSimd<Out>(cx, result));
+ if (!obj)
+ return false;
+ args.rval().setObject(*obj);
+ return true;
+}
+
+// StoreResult can GC, and it is commonly used after pulling something out of a
+// TypedObject:
+//
+// Elem result = op(TypedObjectMemory<Elem>(args[0]));
+// StoreResult<Out>(..., result);
+//
+// The pointer extracted from the typed object in args[0] in the above example
+// could be an interior pointer, and therefore be invalidated by GC.
+// TypedObjectMemory() requires an assertion token to be passed in to prove
+// that we won't GC, but the scope of eg an AutoCheckCannotGC RAII object
+// extends to the end of its containing scope -- which would include the call
+// to StoreResult, resulting in a rooting hazard.
+//
+// TypedObjectElemArray fixes this by wrapping the problematic pointer in a
+// type, and the analysis is able to see that it is dead before calling
+// StoreResult. (But if another GC called is made before the pointer is dead,
+// it will correctly report a hazard.)
+//
+template <typename Elem>
+class TypedObjectElemArray {
+ Elem* elements;
+ public:
+ explicit TypedObjectElemArray(HandleValue objVal) {
+ JS::AutoCheckCannotGC nogc;
+ elements = TypedObjectMemory<Elem*>(objVal, nogc);
+ }
+ Elem& operator[](int i) { return elements[i]; }
+} JS_HAZ_GC_POINTER;
+
+// Coerces the inputs of type In to the type Coercion, apply the operator Op
+// and converts the result to the type Out.
+template<typename In, typename Coercion, template<typename C> class Op, typename Out>
+static bool
+CoercedUnaryFunc(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename Coercion::Elem CoercionElem;
+ typedef typename Out::Elem RetElem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1 || !IsVectorObject<In>(args[0]))
+ return ErrorBadArgs(cx);
+
+ CoercionElem result[Coercion::lanes];
+ TypedObjectElemArray<CoercionElem> val(args[0]);
+ for (unsigned i = 0; i < Coercion::lanes; i++)
+ result[i] = Op<CoercionElem>::apply(val[i]);
+ return StoreResult<Out>(cx, args, (RetElem*) result);
+}
+
+// Coerces the inputs of type In to the type Coercion, apply the operator Op
+// and converts the result to the type Out.
+template<typename In, typename Coercion, template<typename C> class Op, typename Out>
+static bool
+CoercedBinaryFunc(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename Coercion::Elem CoercionElem;
+ typedef typename Out::Elem RetElem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 2 || !IsVectorObject<In>(args[0]) || !IsVectorObject<In>(args[1]))
+ return ErrorBadArgs(cx);
+
+ CoercionElem result[Coercion::lanes];
+ TypedObjectElemArray<CoercionElem> left(args[0]);
+ TypedObjectElemArray<CoercionElem> right(args[1]);
+ for (unsigned i = 0; i < Coercion::lanes; i++)
+ result[i] = Op<CoercionElem>::apply(left[i], right[i]);
+ return StoreResult<Out>(cx, args, (RetElem*) result);
+}
+
+// Same as above, with no coercion, i.e. Coercion == In.
+template<typename In, template<typename C> class Op, typename Out>
+static bool
+UnaryFunc(JSContext* cx, unsigned argc, Value* vp)
+{
+ return CoercedUnaryFunc<In, Out, Op, Out>(cx, argc, vp);
+}
+
+template<typename In, template<typename C> class Op, typename Out>
+static bool
+BinaryFunc(JSContext* cx, unsigned argc, Value* vp)
+{
+ return CoercedBinaryFunc<In, Out, Op, Out>(cx, argc, vp);
+}
+
+template<typename V>
+static bool
+ExtractLane(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename V::Elem Elem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() < 2 || !IsVectorObject<V>(args[0]))
+ return ErrorBadArgs(cx);
+
+ unsigned lane;
+ if (!ArgumentToLaneIndex(cx, args[1], V::lanes, &lane))
+ return false;
+
+ JS::AutoCheckCannotGC nogc(cx);
+ Elem* vec = TypedObjectMemory<Elem*>(args[0], nogc);
+ Elem val = vec[lane];
+ args.rval().set(V::ToValue(val));
+ return true;
+}
+
+template<typename V>
+static bool
+AllTrue(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename V::Elem Elem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() < 1 || !IsVectorObject<V>(args[0]))
+ return ErrorBadArgs(cx);
+
+ JS::AutoCheckCannotGC nogc(cx);
+ Elem* vec = TypedObjectMemory<Elem*>(args[0], nogc);
+ bool allTrue = true;
+ for (unsigned i = 0; allTrue && i < V::lanes; i++)
+ allTrue = vec[i];
+
+ args.rval().setBoolean(allTrue);
+ return true;
+}
+
+template<typename V>
+static bool
+AnyTrue(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename V::Elem Elem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() < 1 || !IsVectorObject<V>(args[0]))
+ return ErrorBadArgs(cx);
+
+ JS::AutoCheckCannotGC nogc(cx);
+ Elem* vec = TypedObjectMemory<Elem*>(args[0], nogc);
+ bool anyTrue = false;
+ for (unsigned i = 0; !anyTrue && i < V::lanes; i++)
+ anyTrue = vec[i];
+
+ args.rval().setBoolean(anyTrue);
+ return true;
+}
+
+template<typename V>
+static bool
+ReplaceLane(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename V::Elem Elem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ // Only the first and second arguments are mandatory
+ if (args.length() < 2 || !IsVectorObject<V>(args[0]))
+ return ErrorBadArgs(cx);
+
+ unsigned lane;
+ if (!ArgumentToLaneIndex(cx, args[1], V::lanes, &lane))
+ return false;
+
+ Elem value;
+ if (!V::Cast(cx, args.get(2), &value))
+ return false;
+
+ TypedObjectElemArray<Elem> vec(args[0]);
+ Elem result[V::lanes];
+ for (unsigned i = 0; i < V::lanes; i++)
+ result[i] = i == lane ? value : vec[i];
+
+ return StoreResult<V>(cx, args, result);
+}
+
+template<typename V>
+static bool
+Swizzle(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename V::Elem Elem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != (V::lanes + 1) || !IsVectorObject<V>(args[0]))
+ return ErrorBadArgs(cx);
+
+ unsigned lanes[V::lanes];
+ for (unsigned i = 0; i < V::lanes; i++) {
+ if (!ArgumentToLaneIndex(cx, args[i + 1], V::lanes, &lanes[i]))
+ return false;
+ }
+
+ TypedObjectElemArray<Elem> val(args[0]);
+ Elem result[V::lanes];
+ for (unsigned i = 0; i < V::lanes; i++)
+ result[i] = val[lanes[i]];
+
+ return StoreResult<V>(cx, args, result);
+}
+
+template<typename V>
+static bool
+Shuffle(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename V::Elem Elem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != (V::lanes + 2) || !IsVectorObject<V>(args[0]) || !IsVectorObject<V>(args[1]))
+ return ErrorBadArgs(cx);
+
+ unsigned lanes[V::lanes];
+ for (unsigned i = 0; i < V::lanes; i++) {
+ if (!ArgumentToLaneIndex(cx, args[i + 2], 2 * V::lanes, &lanes[i]))
+ return false;
+ }
+
+ Elem result[V::lanes];
+ {
+ JS::AutoCheckCannotGC nogc(cx);
+ Elem* lhs = TypedObjectMemory<Elem*>(args[0], nogc);
+ Elem* rhs = TypedObjectMemory<Elem*>(args[1], nogc);
+
+ for (unsigned i = 0; i < V::lanes; i++) {
+ Elem* selectedInput = lanes[i] < V::lanes ? lhs : rhs;
+ result[i] = selectedInput[lanes[i] % V::lanes];
+ }
+ }
+
+ return StoreResult<V>(cx, args, result);
+}
+
+template<typename V, template<typename T> class Op>
+static bool
+BinaryScalar(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename V::Elem Elem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 2)
+ return ErrorBadArgs(cx);
+
+ if (!IsVectorObject<V>(args[0]))
+ return ErrorBadArgs(cx);
+
+ int32_t bits;
+ if (!ToInt32(cx, args[1], &bits))
+ return false;
+
+ TypedObjectElemArray<Elem> val(args[0]);
+ Elem result[V::lanes];
+ for (unsigned i = 0; i < V::lanes; i++)
+ result[i] = Op<Elem>::apply(val[i], bits);
+
+ return StoreResult<V>(cx, args, result);
+}
+
+template<typename In, template<typename C> class Op, typename Out>
+static bool
+CompareFunc(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename In::Elem InElem;
+ typedef typename Out::Elem OutElem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 2 || !IsVectorObject<In>(args[0]) || !IsVectorObject<In>(args[1]))
+ return ErrorBadArgs(cx);
+
+ OutElem result[Out::lanes];
+ TypedObjectElemArray<InElem> left(args[0]);
+ TypedObjectElemArray<InElem> right(args[1]);
+ for (unsigned i = 0; i < Out::lanes; i++) {
+ unsigned j = (i * In::lanes) / Out::lanes;
+ result[i] = Op<InElem>::apply(left[j], right[j]) ? -1 : 0;
+ }
+
+ return StoreResult<Out>(cx, args, result);
+}
+
+// This struct defines whether we should throw during a conversion attempt,
+// when trying to convert a value of type from From to the type To. This
+// happens whenever a C++ conversion would have undefined behavior (and perhaps
+// be platform-dependent).
+template<typename From, typename To>
+struct ThrowOnConvert;
+
+struct NeverThrow
+{
+ static bool value(int32_t v) {
+ return false;
+ }
+};
+
+// While int32 to float conversions can be lossy, these conversions have
+// defined behavior in C++, so we don't need to care about them here. In practice,
+// this means round to nearest, tie with even (zero bit in significand).
+template<>
+struct ThrowOnConvert<int32_t, float> : public NeverThrow {};
+
+template<>
+struct ThrowOnConvert<uint32_t, float> : public NeverThrow {};
+
+// All int32 can be safely converted to doubles.
+template<>
+struct ThrowOnConvert<int32_t, double> : public NeverThrow {};
+
+template<>
+struct ThrowOnConvert<uint32_t, double> : public NeverThrow {};
+
+// All floats can be safely converted to doubles.
+template<>
+struct ThrowOnConvert<float, double> : public NeverThrow {};
+
+// Double to float conversion for inputs which aren't in the float range are
+// undefined behavior in C++, but they're defined in IEEE754.
+template<>
+struct ThrowOnConvert<double, float> : public NeverThrow {};
+
+// Float to integer conversions have undefined behavior if the float value
+// is out of the representable integer range (on x86, will yield the undefined
+// value pattern, namely 0x80000000; on arm, will clamp the input value), so
+// check this here.
+template<typename From, typename IntegerType>
+struct ThrowIfNotInRange
+{
+ static_assert(mozilla::IsIntegral<IntegerType>::value, "bad destination type");
+
+ static bool value(From v) {
+ // Truncate to integer value before the range check.
+ double d = trunc(double(v));
+ // Arrange relations so NaN returns true (i.e., it throws a RangeError).
+ return !(d >= double(mozilla::MinValue<IntegerType>::value) &&
+ d <= double(mozilla::MaxValue<IntegerType>::value));
+ }
+};
+
+template<>
+struct ThrowOnConvert<double, int32_t> : public ThrowIfNotInRange<double, int32_t> {};
+
+template<>
+struct ThrowOnConvert<double, uint32_t> : public ThrowIfNotInRange<double, uint32_t> {};
+
+template<>
+struct ThrowOnConvert<float, int32_t> : public ThrowIfNotInRange<float, int32_t> {};
+
+template<>
+struct ThrowOnConvert<float, uint32_t> : public ThrowIfNotInRange<float, uint32_t> {};
+
+template<typename V, typename Vret>
+static bool
+FuncConvert(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename V::Elem Elem;
+ typedef typename Vret::Elem RetElem;
+
+ static_assert(!mozilla::IsSame<V,Vret>::value, "Can't convert SIMD type to itself");
+ static_assert(V::lanes == Vret::lanes, "Can only convert from same number of lanes");
+ static_assert(!mozilla::IsIntegral<Elem>::value || !mozilla::IsIntegral<RetElem>::value,
+ "Cannot convert between integer SIMD types");
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1 || !IsVectorObject<V>(args[0]))
+ return ErrorBadArgs(cx);
+
+ TypedObjectElemArray<Elem> val(args[0]);
+ RetElem result[Vret::lanes];
+ for (unsigned i = 0; i < V::lanes; i++) {
+ if (ThrowOnConvert<Elem, RetElem>::value(val[i])) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SIMD_FAILED_CONVERSION);
+ return false;
+ }
+ result[i] = ConvertScalar<RetElem>(val[i]);
+ }
+
+ return StoreResult<Vret>(cx, args, result);
+}
+
+template<typename V, typename Vret>
+static bool
+FuncConvertBits(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename V::Elem Elem;
+ typedef typename Vret::Elem RetElem;
+
+ static_assert(!mozilla::IsSame<V, Vret>::value, "Can't convert SIMD type to itself");
+ static_assert(V::lanes * sizeof(Elem) == Vret::lanes * sizeof(RetElem),
+ "Can only bitcast from the same number of bits");
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1 || !IsVectorObject<V>(args[0]))
+ return ErrorBadArgs(cx);
+
+ // While we could just pass the typedMem of args[0] as StoreResults' last
+ // argument, a GC could move the pointer to its memory in the meanwhile.
+ // For consistency with other SIMD functions, simply copy the input in a
+ // temporary array.
+ RetElem copy[Vret::lanes];
+ {
+ JS::AutoCheckCannotGC nogc(cx);
+ memcpy(copy, TypedObjectMemory<RetElem*>(args[0], nogc), Vret::lanes * sizeof(RetElem));
+ }
+ return StoreResult<Vret>(cx, args, copy);
+}
+
+template<typename Vret>
+static bool
+FuncSplat(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename Vret::Elem RetElem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RetElem arg;
+ if (!Vret::Cast(cx, args.get(0), &arg))
+ return false;
+
+ RetElem result[Vret::lanes];
+ for (unsigned i = 0; i < Vret::lanes; i++)
+ result[i] = arg;
+ return StoreResult<Vret>(cx, args, result);
+}
+
+template<typename V>
+static bool
+Bool(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename V::Elem Elem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Elem result[V::lanes];
+ for (unsigned i = 0; i < V::lanes; i++)
+ result[i] = ToBoolean(args.get(i)) ? -1 : 0;
+ return StoreResult<V>(cx, args, result);
+}
+
+template<typename V, typename MaskType>
+static bool
+SelectBits(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename V::Elem Elem;
+ typedef typename MaskType::Elem MaskTypeElem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 3 || !IsVectorObject<MaskType>(args[0]) ||
+ !IsVectorObject<V>(args[1]) || !IsVectorObject<V>(args[2]))
+ {
+ return ErrorBadArgs(cx);
+ }
+
+ TypedObjectElemArray<MaskTypeElem> val(args[0]);
+ TypedObjectElemArray<MaskTypeElem> tv(args[1]);
+ TypedObjectElemArray<MaskTypeElem> fv(args[2]);
+
+ MaskTypeElem tr[MaskType::lanes];
+ for (unsigned i = 0; i < MaskType::lanes; i++)
+ tr[i] = And<MaskTypeElem>::apply(val[i], tv[i]);
+
+ MaskTypeElem fr[MaskType::lanes];
+ for (unsigned i = 0; i < MaskType::lanes; i++)
+ fr[i] = And<MaskTypeElem>::apply(Not<MaskTypeElem>::apply(val[i]), fv[i]);
+
+ MaskTypeElem orInt[MaskType::lanes];
+ for (unsigned i = 0; i < MaskType::lanes; i++)
+ orInt[i] = Or<MaskTypeElem>::apply(tr[i], fr[i]);
+
+ Elem* result = reinterpret_cast<Elem*>(orInt);
+ return StoreResult<V>(cx, args, result);
+}
+
+template<typename V, typename MaskType>
+static bool
+Select(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename V::Elem Elem;
+ typedef typename MaskType::Elem MaskTypeElem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 3 || !IsVectorObject<MaskType>(args[0]) ||
+ !IsVectorObject<V>(args[1]) || !IsVectorObject<V>(args[2]))
+ {
+ return ErrorBadArgs(cx);
+ }
+
+ TypedObjectElemArray<MaskTypeElem> mask(args[0]);
+ TypedObjectElemArray<Elem> tv(args[1]);
+ TypedObjectElemArray<Elem> fv(args[2]);
+
+ Elem result[V::lanes];
+ for (unsigned i = 0; i < V::lanes; i++)
+ result[i] = mask[i] ? tv[i] : fv[i];
+
+ return StoreResult<V>(cx, args, result);
+}
+
+// Extract an integer lane index from a function argument.
+//
+// Register an exception and return false if the argument is not suitable.
+static bool
+ArgumentToLaneIndex(JSContext* cx, JS::HandleValue v, unsigned limit, unsigned* lane)
+{
+ uint64_t arg;
+ if (!ToIntegerIndex(cx, v, &arg))
+ return false;
+ if (arg >= limit)
+ return ErrorBadIndex(cx);
+
+ *lane = unsigned(arg);
+ return true;
+}
+
+// Look for arguments (ta, idx) where ta is a TypedArray and idx is a
+// non-negative integer.
+// Check that accessBytes can be accessed starting from index idx in the array.
+// Return the array handle in typedArray and idx converted to a byte offset in byteStart.
+static bool
+TypedArrayFromArgs(JSContext* cx, const CallArgs& args, uint32_t accessBytes,
+ MutableHandleObject typedArray, size_t* byteStart)
+{
+ if (!args[0].isObject())
+ return ErrorBadArgs(cx);
+
+ JSObject& argobj = args[0].toObject();
+ if (!argobj.is<TypedArrayObject>())
+ return ErrorBadArgs(cx);
+
+ typedArray.set(&argobj);
+
+ uint64_t index;
+ if (!ToIntegerIndex(cx, args[1], &index))
+ return false;
+
+ // Do the range check in 64 bits even when size_t is 32 bits.
+ // This can't overflow because index <= 2^53.
+ uint64_t bytes = index * typedArray->as<TypedArrayObject>().bytesPerElement();
+ // Keep in sync with AsmJS OnOutOfBounds function.
+ if ((bytes + accessBytes) > typedArray->as<TypedArrayObject>().byteLength())
+ return ErrorBadIndex(cx);
+
+ *byteStart = bytes;
+
+ return true;
+}
+
+template<class V, unsigned NumElem>
+static bool
+Load(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename V::Elem Elem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 2)
+ return ErrorBadArgs(cx);
+
+ size_t byteStart;
+ RootedObject typedArray(cx);
+ if (!TypedArrayFromArgs(cx, args, sizeof(Elem) * NumElem, &typedArray, &byteStart))
+ return false;
+
+ Rooted<TypeDescr*> typeDescr(cx, GetTypeDescr<V>(cx));
+ if (!typeDescr)
+ return false;
+
+ Rooted<TypedObject*> result(cx, TypedObject::createZeroed(cx, typeDescr, 0));
+ if (!result)
+ return false;
+
+ JS::AutoCheckCannotGC nogc(cx);
+ SharedMem<Elem*> src =
+ typedArray->as<TypedArrayObject>().viewDataEither().addBytes(byteStart).cast<Elem*>();
+ Elem* dst = reinterpret_cast<Elem*>(result->typedMem(nogc));
+ jit::AtomicOperations::podCopySafeWhenRacy(SharedMem<Elem*>::unshared(dst), src, NumElem);
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+template<class V, unsigned NumElem>
+static bool
+Store(JSContext* cx, unsigned argc, Value* vp)
+{
+ typedef typename V::Elem Elem;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 3)
+ return ErrorBadArgs(cx);
+
+ size_t byteStart;
+ RootedObject typedArray(cx);
+ if (!TypedArrayFromArgs(cx, args, sizeof(Elem) * NumElem, &typedArray, &byteStart))
+ return false;
+
+ if (!IsVectorObject<V>(args[2]))
+ return ErrorBadArgs(cx);
+
+ JS::AutoCheckCannotGC nogc(cx);
+ Elem* src = TypedObjectMemory<Elem*>(args[2], nogc);
+ SharedMem<Elem*> dst =
+ typedArray->as<TypedArrayObject>().viewDataEither().addBytes(byteStart).cast<Elem*>();
+ js::jit::AtomicOperations::podCopySafeWhenRacy(dst, SharedMem<Elem*>::unshared(src), NumElem);
+
+ args.rval().setObject(args[2].toObject());
+ return true;
+}
+
+#define DEFINE_SIMD_FLOAT32X4_FUNCTION(Name, Func, Operands) \
+bool \
+js::simd_float32x4_##Name(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ return Func(cx, argc, vp); \
+}
+FLOAT32X4_FUNCTION_LIST(DEFINE_SIMD_FLOAT32X4_FUNCTION)
+#undef DEFINE_SIMD_FLOAT32X4_FUNCTION
+
+#define DEFINE_SIMD_FLOAT64X2_FUNCTION(Name, Func, Operands) \
+bool \
+js::simd_float64x2_##Name(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ return Func(cx, argc, vp); \
+}
+FLOAT64X2_FUNCTION_LIST(DEFINE_SIMD_FLOAT64X2_FUNCTION)
+#undef DEFINE_SIMD_FLOAT64X2_FUNCTION
+
+#define DEFINE_SIMD_INT8X16_FUNCTION(Name, Func, Operands) \
+bool \
+js::simd_int8x16_##Name(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ return Func(cx, argc, vp); \
+}
+INT8X16_FUNCTION_LIST(DEFINE_SIMD_INT8X16_FUNCTION)
+#undef DEFINE_SIMD_INT8X16_FUNCTION
+
+#define DEFINE_SIMD_INT16X8_FUNCTION(Name, Func, Operands) \
+bool \
+js::simd_int16x8_##Name(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ return Func(cx, argc, vp); \
+}
+INT16X8_FUNCTION_LIST(DEFINE_SIMD_INT16X8_FUNCTION)
+#undef DEFINE_SIMD_INT16X8_FUNCTION
+
+#define DEFINE_SIMD_INT32X4_FUNCTION(Name, Func, Operands) \
+bool \
+js::simd_int32x4_##Name(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ return Func(cx, argc, vp); \
+}
+INT32X4_FUNCTION_LIST(DEFINE_SIMD_INT32X4_FUNCTION)
+#undef DEFINE_SIMD_INT32X4_FUNCTION
+
+#define DEFINE_SIMD_UINT8X16_FUNCTION(Name, Func, Operands) \
+bool \
+js::simd_uint8x16_##Name(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ return Func(cx, argc, vp); \
+}
+UINT8X16_FUNCTION_LIST(DEFINE_SIMD_UINT8X16_FUNCTION)
+#undef DEFINE_SIMD_UINT8X16_FUNCTION
+
+#define DEFINE_SIMD_UINT16X8_FUNCTION(Name, Func, Operands) \
+bool \
+js::simd_uint16x8_##Name(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ return Func(cx, argc, vp); \
+}
+UINT16X8_FUNCTION_LIST(DEFINE_SIMD_UINT16X8_FUNCTION)
+#undef DEFINE_SIMD_UINT16X8_FUNCTION
+
+#define DEFINE_SIMD_UINT32X4_FUNCTION(Name, Func, Operands) \
+bool \
+js::simd_uint32x4_##Name(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ return Func(cx, argc, vp); \
+}
+UINT32X4_FUNCTION_LIST(DEFINE_SIMD_UINT32X4_FUNCTION)
+#undef DEFINE_SIMD_UINT32X4_FUNCTION
+
+#define DEFINE_SIMD_BOOL8X16_FUNCTION(Name, Func, Operands) \
+bool \
+js::simd_bool8x16_##Name(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ return Func(cx, argc, vp); \
+}
+
+BOOL8X16_FUNCTION_LIST(DEFINE_SIMD_BOOL8X16_FUNCTION)
+#undef DEFINE_SIMD_BOOL8X16_FUNCTION
+
+#define DEFINE_SIMD_BOOL16X8_FUNCTION(Name, Func, Operands) \
+bool \
+js::simd_bool16x8_##Name(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ return Func(cx, argc, vp); \
+}
+BOOL16X8_FUNCTION_LIST(DEFINE_SIMD_BOOL16X8_FUNCTION)
+#undef DEFINE_SIMD_BOOL16X8_FUNCTION
+
+#define DEFINE_SIMD_BOOL32X4_FUNCTION(Name, Func, Operands) \
+bool \
+js::simd_bool32x4_##Name(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ return Func(cx, argc, vp); \
+}
+BOOL32X4_FUNCTION_LIST(DEFINE_SIMD_BOOL32X4_FUNCTION)
+#undef DEFINE_SIMD_BOOL32X4_FUNCTION
+
+#define DEFINE_SIMD_BOOL64X2_FUNCTION(Name, Func, Operands) \
+bool \
+js::simd_bool64x2_##Name(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ return Func(cx, argc, vp); \
+}
+BOOL64X2_FUNCTION_LIST(DEFINE_SIMD_BOOL64X2_FUNCTION)
+#undef DEFINE_SIMD_BOOL64X2_FUNCTION
diff --git a/js/src/builtin/SIMD.h b/js/src/builtin/SIMD.h
new file mode 100644
index 000000000..393e7df74
--- /dev/null
+++ b/js/src/builtin/SIMD.h
@@ -0,0 +1,1219 @@
+/* -*- 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/. */
+
+#ifndef builtin_SIMD_h
+#define builtin_SIMD_h
+
+#include "jsapi.h"
+#include "NamespaceImports.h"
+
+#include "builtin/TypedObjectConstants.h"
+#include "jit/IonTypes.h"
+#include "js/Conversions.h"
+
+/*
+ * JS SIMD functions.
+ * Spec matching polyfill:
+ * https://github.com/tc39/ecmascript_simd/blob/master/src/ecmascript_simd.js
+ */
+
+// Bool8x16.
+#define BOOL8X16_UNARY_FUNCTION_LIST(V) \
+ V(not, (UnaryFunc<Bool8x16, LogicalNot, Bool8x16>), 1) \
+ V(check, (UnaryFunc<Bool8x16, Identity, Bool8x16>), 1) \
+ V(splat, (FuncSplat<Bool8x16>), 1) \
+ V(allTrue, (AllTrue<Bool8x16>), 1) \
+ V(anyTrue, (AnyTrue<Bool8x16>), 1)
+
+#define BOOL8X16_BINARY_FUNCTION_LIST(V) \
+ V(extractLane, (ExtractLane<Bool8x16>), 2) \
+ V(and, (BinaryFunc<Bool8x16, And, Bool8x16>), 2) \
+ V(or, (BinaryFunc<Bool8x16, Or, Bool8x16>), 2) \
+ V(xor, (BinaryFunc<Bool8x16, Xor, Bool8x16>), 2) \
+
+#define BOOL8X16_TERNARY_FUNCTION_LIST(V) \
+ V(replaceLane, (ReplaceLane<Bool8x16>), 3)
+
+#define BOOL8X16_FUNCTION_LIST(V) \
+ BOOL8X16_UNARY_FUNCTION_LIST(V) \
+ BOOL8X16_BINARY_FUNCTION_LIST(V) \
+ BOOL8X16_TERNARY_FUNCTION_LIST(V)
+
+// Bool 16x8.
+#define BOOL16X8_UNARY_FUNCTION_LIST(V) \
+ V(not, (UnaryFunc<Bool16x8, LogicalNot, Bool16x8>), 1) \
+ V(check, (UnaryFunc<Bool16x8, Identity, Bool16x8>), 1) \
+ V(splat, (FuncSplat<Bool16x8>), 1) \
+ V(allTrue, (AllTrue<Bool16x8>), 1) \
+ V(anyTrue, (AnyTrue<Bool16x8>), 1)
+
+#define BOOL16X8_BINARY_FUNCTION_LIST(V) \
+ V(extractLane, (ExtractLane<Bool16x8>), 2) \
+ V(and, (BinaryFunc<Bool16x8, And, Bool16x8>), 2) \
+ V(or, (BinaryFunc<Bool16x8, Or, Bool16x8>), 2) \
+ V(xor, (BinaryFunc<Bool16x8, Xor, Bool16x8>), 2) \
+
+#define BOOL16X8_TERNARY_FUNCTION_LIST(V) \
+ V(replaceLane, (ReplaceLane<Bool16x8>), 3)
+
+#define BOOL16X8_FUNCTION_LIST(V) \
+ BOOL16X8_UNARY_FUNCTION_LIST(V) \
+ BOOL16X8_BINARY_FUNCTION_LIST(V) \
+ BOOL16X8_TERNARY_FUNCTION_LIST(V)
+
+// Bool32x4.
+#define BOOL32X4_UNARY_FUNCTION_LIST(V) \
+ V(not, (UnaryFunc<Bool32x4, LogicalNot, Bool32x4>), 1) \
+ V(check, (UnaryFunc<Bool32x4, Identity, Bool32x4>), 1) \
+ V(splat, (FuncSplat<Bool32x4>), 1) \
+ V(allTrue, (AllTrue<Bool32x4>), 1) \
+ V(anyTrue, (AnyTrue<Bool32x4>), 1)
+
+#define BOOL32X4_BINARY_FUNCTION_LIST(V) \
+ V(extractLane, (ExtractLane<Bool32x4>), 2) \
+ V(and, (BinaryFunc<Bool32x4, And, Bool32x4>), 2) \
+ V(or, (BinaryFunc<Bool32x4, Or, Bool32x4>), 2) \
+ V(xor, (BinaryFunc<Bool32x4, Xor, Bool32x4>), 2) \
+
+#define BOOL32X4_TERNARY_FUNCTION_LIST(V) \
+ V(replaceLane, (ReplaceLane<Bool32x4>), 3)
+
+#define BOOL32X4_FUNCTION_LIST(V) \
+ BOOL32X4_UNARY_FUNCTION_LIST(V) \
+ BOOL32X4_BINARY_FUNCTION_LIST(V) \
+ BOOL32X4_TERNARY_FUNCTION_LIST(V)
+
+// Bool64x2.
+#define BOOL64X2_UNARY_FUNCTION_LIST(V) \
+ V(not, (UnaryFunc<Bool64x2, LogicalNot, Bool64x2>), 1) \
+ V(check, (UnaryFunc<Bool64x2, Identity, Bool64x2>), 1) \
+ V(splat, (FuncSplat<Bool64x2>), 1) \
+ V(allTrue, (AllTrue<Bool64x2>), 1) \
+ V(anyTrue, (AnyTrue<Bool64x2>), 1)
+
+#define BOOL64X2_BINARY_FUNCTION_LIST(V) \
+ V(extractLane, (ExtractLane<Bool64x2>), 2) \
+ V(and, (BinaryFunc<Bool64x2, And, Bool64x2>), 2) \
+ V(or, (BinaryFunc<Bool64x2, Or, Bool64x2>), 2) \
+ V(xor, (BinaryFunc<Bool64x2, Xor, Bool64x2>), 2) \
+
+#define BOOL64X2_TERNARY_FUNCTION_LIST(V) \
+ V(replaceLane, (ReplaceLane<Bool64x2>), 3)
+
+#define BOOL64X2_FUNCTION_LIST(V) \
+ BOOL64X2_UNARY_FUNCTION_LIST(V) \
+ BOOL64X2_BINARY_FUNCTION_LIST(V) \
+ BOOL64X2_TERNARY_FUNCTION_LIST(V)
+
+// Float32x4.
+#define FLOAT32X4_UNARY_FUNCTION_LIST(V) \
+ V(abs, (UnaryFunc<Float32x4, Abs, Float32x4>), 1) \
+ V(check, (UnaryFunc<Float32x4, Identity, Float32x4>), 1) \
+ V(fromFloat64x2Bits, (FuncConvertBits<Float64x2, Float32x4>), 1) \
+ V(fromInt8x16Bits, (FuncConvertBits<Int8x16, Float32x4>), 1) \
+ V(fromInt16x8Bits, (FuncConvertBits<Int16x8, Float32x4>), 1) \
+ V(fromInt32x4, (FuncConvert<Int32x4, Float32x4>), 1) \
+ V(fromInt32x4Bits, (FuncConvertBits<Int32x4, Float32x4>), 1) \
+ V(fromUint8x16Bits, (FuncConvertBits<Uint8x16, Float32x4>), 1) \
+ V(fromUint16x8Bits, (FuncConvertBits<Uint16x8, Float32x4>), 1) \
+ V(fromUint32x4, (FuncConvert<Uint32x4, Float32x4>), 1) \
+ V(fromUint32x4Bits, (FuncConvertBits<Uint32x4, Float32x4>), 1) \
+ V(neg, (UnaryFunc<Float32x4, Neg, Float32x4>), 1) \
+ V(reciprocalApproximation, (UnaryFunc<Float32x4, RecApprox, Float32x4>), 1) \
+ V(reciprocalSqrtApproximation, (UnaryFunc<Float32x4, RecSqrtApprox, Float32x4>), 1) \
+ V(splat, (FuncSplat<Float32x4>), 1) \
+ V(sqrt, (UnaryFunc<Float32x4, Sqrt, Float32x4>), 1)
+
+#define FLOAT32X4_BINARY_FUNCTION_LIST(V) \
+ V(add, (BinaryFunc<Float32x4, Add, Float32x4>), 2) \
+ V(div, (BinaryFunc<Float32x4, Div, Float32x4>), 2) \
+ V(equal, (CompareFunc<Float32x4, Equal, Bool32x4>), 2) \
+ V(extractLane, (ExtractLane<Float32x4>), 2) \
+ V(greaterThan, (CompareFunc<Float32x4, GreaterThan, Bool32x4>), 2) \
+ V(greaterThanOrEqual, (CompareFunc<Float32x4, GreaterThanOrEqual, Bool32x4>), 2) \
+ V(lessThan, (CompareFunc<Float32x4, LessThan, Bool32x4>), 2) \
+ V(lessThanOrEqual, (CompareFunc<Float32x4, LessThanOrEqual, Bool32x4>), 2) \
+ V(load, (Load<Float32x4, 4>), 2) \
+ V(load3, (Load<Float32x4, 3>), 2) \
+ V(load2, (Load<Float32x4, 2>), 2) \
+ V(load1, (Load<Float32x4, 1>), 2) \
+ V(max, (BinaryFunc<Float32x4, Maximum, Float32x4>), 2) \
+ V(maxNum, (BinaryFunc<Float32x4, MaxNum, Float32x4>), 2) \
+ V(min, (BinaryFunc<Float32x4, Minimum, Float32x4>), 2) \
+ V(minNum, (BinaryFunc<Float32x4, MinNum, Float32x4>), 2) \
+ V(mul, (BinaryFunc<Float32x4, Mul, Float32x4>), 2) \
+ V(notEqual, (CompareFunc<Float32x4, NotEqual, Bool32x4>), 2) \
+ V(sub, (BinaryFunc<Float32x4, Sub, Float32x4>), 2)
+
+#define FLOAT32X4_TERNARY_FUNCTION_LIST(V) \
+ V(replaceLane, (ReplaceLane<Float32x4>), 3) \
+ V(select, (Select<Float32x4, Bool32x4>), 3) \
+ V(store, (Store<Float32x4, 4>), 3) \
+ V(store3, (Store<Float32x4, 3>), 3) \
+ V(store2, (Store<Float32x4, 2>), 3) \
+ V(store1, (Store<Float32x4, 1>), 3)
+
+#define FLOAT32X4_SHUFFLE_FUNCTION_LIST(V) \
+ V(swizzle, Swizzle<Float32x4>, 5) \
+ V(shuffle, Shuffle<Float32x4>, 6)
+
+#define FLOAT32X4_FUNCTION_LIST(V) \
+ FLOAT32X4_UNARY_FUNCTION_LIST(V) \
+ FLOAT32X4_BINARY_FUNCTION_LIST(V) \
+ FLOAT32X4_TERNARY_FUNCTION_LIST(V) \
+ FLOAT32X4_SHUFFLE_FUNCTION_LIST(V)
+
+// Float64x2.
+#define FLOAT64X2_UNARY_FUNCTION_LIST(V) \
+ V(abs, (UnaryFunc<Float64x2, Abs, Float64x2>), 1) \
+ V(check, (UnaryFunc<Float64x2, Identity, Float64x2>), 1) \
+ V(fromFloat32x4Bits, (FuncConvertBits<Float32x4, Float64x2>), 1) \
+ V(fromInt8x16Bits, (FuncConvertBits<Int8x16, Float64x2>), 1) \
+ V(fromInt16x8Bits, (FuncConvertBits<Int16x8, Float64x2>), 1) \
+ V(fromInt32x4Bits, (FuncConvertBits<Int32x4, Float64x2>), 1) \
+ V(fromUint8x16Bits, (FuncConvertBits<Uint8x16, Float64x2>), 1) \
+ V(fromUint16x8Bits, (FuncConvertBits<Uint16x8, Float64x2>), 1) \
+ V(fromUint32x4Bits, (FuncConvertBits<Uint32x4, Float64x2>), 1) \
+ V(neg, (UnaryFunc<Float64x2, Neg, Float64x2>), 1) \
+ V(reciprocalApproximation, (UnaryFunc<Float64x2, RecApprox, Float64x2>), 1) \
+ V(reciprocalSqrtApproximation, (UnaryFunc<Float64x2, RecSqrtApprox, Float64x2>), 1) \
+ V(splat, (FuncSplat<Float64x2>), 1) \
+ V(sqrt, (UnaryFunc<Float64x2, Sqrt, Float64x2>), 1)
+
+#define FLOAT64X2_BINARY_FUNCTION_LIST(V) \
+ V(add, (BinaryFunc<Float64x2, Add, Float64x2>), 2) \
+ V(div, (BinaryFunc<Float64x2, Div, Float64x2>), 2) \
+ V(equal, (CompareFunc<Float64x2, Equal, Bool64x2>), 2) \
+ V(extractLane, (ExtractLane<Float64x2>), 2) \
+ V(greaterThan, (CompareFunc<Float64x2, GreaterThan, Bool64x2>), 2) \
+ V(greaterThanOrEqual, (CompareFunc<Float64x2, GreaterThanOrEqual, Bool64x2>), 2) \
+ V(lessThan, (CompareFunc<Float64x2, LessThan, Bool64x2>), 2) \
+ V(lessThanOrEqual, (CompareFunc<Float64x2, LessThanOrEqual, Bool64x2>), 2) \
+ V(load, (Load<Float64x2, 2>), 2) \
+ V(load1, (Load<Float64x2, 1>), 2) \
+ V(max, (BinaryFunc<Float64x2, Maximum, Float64x2>), 2) \
+ V(maxNum, (BinaryFunc<Float64x2, MaxNum, Float64x2>), 2) \
+ V(min, (BinaryFunc<Float64x2, Minimum, Float64x2>), 2) \
+ V(minNum, (BinaryFunc<Float64x2, MinNum, Float64x2>), 2) \
+ V(mul, (BinaryFunc<Float64x2, Mul, Float64x2>), 2) \
+ V(notEqual, (CompareFunc<Float64x2, NotEqual, Bool64x2>), 2) \
+ V(sub, (BinaryFunc<Float64x2, Sub, Float64x2>), 2)
+
+#define FLOAT64X2_TERNARY_FUNCTION_LIST(V) \
+ V(replaceLane, (ReplaceLane<Float64x2>), 3) \
+ V(select, (Select<Float64x2, Bool64x2>), 3) \
+ V(store, (Store<Float64x2, 2>), 3) \
+ V(store1, (Store<Float64x2, 1>), 3)
+
+#define FLOAT64X2_SHUFFLE_FUNCTION_LIST(V) \
+ V(swizzle, Swizzle<Float64x2>, 3) \
+ V(shuffle, Shuffle<Float64x2>, 4)
+
+#define FLOAT64X2_FUNCTION_LIST(V) \
+ FLOAT64X2_UNARY_FUNCTION_LIST(V) \
+ FLOAT64X2_BINARY_FUNCTION_LIST(V) \
+ FLOAT64X2_TERNARY_FUNCTION_LIST(V) \
+ FLOAT64X2_SHUFFLE_FUNCTION_LIST(V)
+
+// Int8x16.
+#define INT8X16_UNARY_FUNCTION_LIST(V) \
+ V(check, (UnaryFunc<Int8x16, Identity, Int8x16>), 1) \
+ V(fromFloat32x4Bits, (FuncConvertBits<Float32x4, Int8x16>), 1) \
+ V(fromFloat64x2Bits, (FuncConvertBits<Float64x2, Int8x16>), 1) \
+ V(fromInt16x8Bits, (FuncConvertBits<Int16x8, Int8x16>), 1) \
+ V(fromInt32x4Bits, (FuncConvertBits<Int32x4, Int8x16>), 1) \
+ V(fromUint8x16Bits, (FuncConvertBits<Uint8x16, Int8x16>), 1) \
+ V(fromUint16x8Bits, (FuncConvertBits<Uint16x8, Int8x16>), 1) \
+ V(fromUint32x4Bits, (FuncConvertBits<Uint32x4, Int8x16>), 1) \
+ V(neg, (UnaryFunc<Int8x16, Neg, Int8x16>), 1) \
+ V(not, (UnaryFunc<Int8x16, Not, Int8x16>), 1) \
+ V(splat, (FuncSplat<Int8x16>), 1)
+
+#define INT8X16_BINARY_FUNCTION_LIST(V) \
+ V(add, (BinaryFunc<Int8x16, Add, Int8x16>), 2) \
+ V(addSaturate, (BinaryFunc<Int8x16, AddSaturate, Int8x16>), 2) \
+ V(and, (BinaryFunc<Int8x16, And, Int8x16>), 2) \
+ V(equal, (CompareFunc<Int8x16, Equal, Bool8x16>), 2) \
+ V(extractLane, (ExtractLane<Int8x16>), 2) \
+ V(greaterThan, (CompareFunc<Int8x16, GreaterThan, Bool8x16>), 2) \
+ V(greaterThanOrEqual, (CompareFunc<Int8x16, GreaterThanOrEqual, Bool8x16>), 2) \
+ V(lessThan, (CompareFunc<Int8x16, LessThan, Bool8x16>), 2) \
+ V(lessThanOrEqual, (CompareFunc<Int8x16, LessThanOrEqual, Bool8x16>), 2) \
+ V(load, (Load<Int8x16, 16>), 2) \
+ V(mul, (BinaryFunc<Int8x16, Mul, Int8x16>), 2) \
+ V(notEqual, (CompareFunc<Int8x16, NotEqual, Bool8x16>), 2) \
+ V(or, (BinaryFunc<Int8x16, Or, Int8x16>), 2) \
+ V(sub, (BinaryFunc<Int8x16, Sub, Int8x16>), 2) \
+ V(subSaturate, (BinaryFunc<Int8x16, SubSaturate, Int8x16>), 2) \
+ V(shiftLeftByScalar, (BinaryScalar<Int8x16, ShiftLeft>), 2) \
+ V(shiftRightByScalar, (BinaryScalar<Int8x16, ShiftRightArithmetic>), 2) \
+ V(xor, (BinaryFunc<Int8x16, Xor, Int8x16>), 2)
+
+#define INT8X16_TERNARY_FUNCTION_LIST(V) \
+ V(replaceLane, (ReplaceLane<Int8x16>), 3) \
+ V(select, (Select<Int8x16, Bool8x16>), 3) \
+ V(store, (Store<Int8x16, 16>), 3)
+
+#define INT8X16_SHUFFLE_FUNCTION_LIST(V) \
+ V(swizzle, Swizzle<Int8x16>, 17) \
+ V(shuffle, Shuffle<Int8x16>, 18)
+
+#define INT8X16_FUNCTION_LIST(V) \
+ INT8X16_UNARY_FUNCTION_LIST(V) \
+ INT8X16_BINARY_FUNCTION_LIST(V) \
+ INT8X16_TERNARY_FUNCTION_LIST(V) \
+ INT8X16_SHUFFLE_FUNCTION_LIST(V)
+
+// Uint8x16.
+#define UINT8X16_UNARY_FUNCTION_LIST(V) \
+ V(check, (UnaryFunc<Uint8x16, Identity, Uint8x16>), 1) \
+ V(fromFloat32x4Bits, (FuncConvertBits<Float32x4, Uint8x16>), 1) \
+ V(fromFloat64x2Bits, (FuncConvertBits<Float64x2, Uint8x16>), 1) \
+ V(fromInt8x16Bits, (FuncConvertBits<Int8x16, Uint8x16>), 1) \
+ V(fromInt16x8Bits, (FuncConvertBits<Int16x8, Uint8x16>), 1) \
+ V(fromInt32x4Bits, (FuncConvertBits<Int32x4, Uint8x16>), 1) \
+ V(fromUint16x8Bits, (FuncConvertBits<Uint16x8, Uint8x16>), 1) \
+ V(fromUint32x4Bits, (FuncConvertBits<Uint32x4, Uint8x16>), 1) \
+ V(neg, (UnaryFunc<Uint8x16, Neg, Uint8x16>), 1) \
+ V(not, (UnaryFunc<Uint8x16, Not, Uint8x16>), 1) \
+ V(splat, (FuncSplat<Uint8x16>), 1)
+
+#define UINT8X16_BINARY_FUNCTION_LIST(V) \
+ V(add, (BinaryFunc<Uint8x16, Add, Uint8x16>), 2) \
+ V(addSaturate, (BinaryFunc<Uint8x16, AddSaturate, Uint8x16>), 2) \
+ V(and, (BinaryFunc<Uint8x16, And, Uint8x16>), 2) \
+ V(equal, (CompareFunc<Uint8x16, Equal, Bool8x16>), 2) \
+ V(extractLane, (ExtractLane<Uint8x16>), 2) \
+ V(greaterThan, (CompareFunc<Uint8x16, GreaterThan, Bool8x16>), 2) \
+ V(greaterThanOrEqual, (CompareFunc<Uint8x16, GreaterThanOrEqual, Bool8x16>), 2) \
+ V(lessThan, (CompareFunc<Uint8x16, LessThan, Bool8x16>), 2) \
+ V(lessThanOrEqual, (CompareFunc<Uint8x16, LessThanOrEqual, Bool8x16>), 2) \
+ V(load, (Load<Uint8x16, 16>), 2) \
+ V(mul, (BinaryFunc<Uint8x16, Mul, Uint8x16>), 2) \
+ V(notEqual, (CompareFunc<Uint8x16, NotEqual, Bool8x16>), 2) \
+ V(or, (BinaryFunc<Uint8x16, Or, Uint8x16>), 2) \
+ V(sub, (BinaryFunc<Uint8x16, Sub, Uint8x16>), 2) \
+ V(subSaturate, (BinaryFunc<Uint8x16, SubSaturate, Uint8x16>), 2) \
+ V(shiftLeftByScalar, (BinaryScalar<Uint8x16, ShiftLeft>), 2) \
+ V(shiftRightByScalar, (BinaryScalar<Uint8x16, ShiftRightLogical>), 2) \
+ V(xor, (BinaryFunc<Uint8x16, Xor, Uint8x16>), 2)
+
+#define UINT8X16_TERNARY_FUNCTION_LIST(V) \
+ V(replaceLane, (ReplaceLane<Uint8x16>), 3) \
+ V(select, (Select<Uint8x16, Bool8x16>), 3) \
+ V(store, (Store<Uint8x16, 16>), 3)
+
+#define UINT8X16_SHUFFLE_FUNCTION_LIST(V) \
+ V(swizzle, Swizzle<Uint8x16>, 17) \
+ V(shuffle, Shuffle<Uint8x16>, 18)
+
+#define UINT8X16_FUNCTION_LIST(V) \
+ UINT8X16_UNARY_FUNCTION_LIST(V) \
+ UINT8X16_BINARY_FUNCTION_LIST(V) \
+ UINT8X16_TERNARY_FUNCTION_LIST(V) \
+ UINT8X16_SHUFFLE_FUNCTION_LIST(V)
+
+// Int16x8.
+#define INT16X8_UNARY_FUNCTION_LIST(V) \
+ V(check, (UnaryFunc<Int16x8, Identity, Int16x8>), 1) \
+ V(fromFloat32x4Bits, (FuncConvertBits<Float32x4, Int16x8>), 1) \
+ V(fromFloat64x2Bits, (FuncConvertBits<Float64x2, Int16x8>), 1) \
+ V(fromInt8x16Bits, (FuncConvertBits<Int8x16, Int16x8>), 1) \
+ V(fromInt32x4Bits, (FuncConvertBits<Int32x4, Int16x8>), 1) \
+ V(fromUint8x16Bits, (FuncConvertBits<Uint8x16, Int16x8>), 1) \
+ V(fromUint16x8Bits, (FuncConvertBits<Uint16x8, Int16x8>), 1) \
+ V(fromUint32x4Bits, (FuncConvertBits<Uint32x4, Int16x8>), 1) \
+ V(neg, (UnaryFunc<Int16x8, Neg, Int16x8>), 1) \
+ V(not, (UnaryFunc<Int16x8, Not, Int16x8>), 1) \
+ V(splat, (FuncSplat<Int16x8>), 1)
+
+#define INT16X8_BINARY_FUNCTION_LIST(V) \
+ V(add, (BinaryFunc<Int16x8, Add, Int16x8>), 2) \
+ V(addSaturate, (BinaryFunc<Int16x8, AddSaturate, Int16x8>), 2) \
+ V(and, (BinaryFunc<Int16x8, And, Int16x8>), 2) \
+ V(equal, (CompareFunc<Int16x8, Equal, Bool16x8>), 2) \
+ V(extractLane, (ExtractLane<Int16x8>), 2) \
+ V(greaterThan, (CompareFunc<Int16x8, GreaterThan, Bool16x8>), 2) \
+ V(greaterThanOrEqual, (CompareFunc<Int16x8, GreaterThanOrEqual, Bool16x8>), 2) \
+ V(lessThan, (CompareFunc<Int16x8, LessThan, Bool16x8>), 2) \
+ V(lessThanOrEqual, (CompareFunc<Int16x8, LessThanOrEqual, Bool16x8>), 2) \
+ V(load, (Load<Int16x8, 8>), 2) \
+ V(mul, (BinaryFunc<Int16x8, Mul, Int16x8>), 2) \
+ V(notEqual, (CompareFunc<Int16x8, NotEqual, Bool16x8>), 2) \
+ V(or, (BinaryFunc<Int16x8, Or, Int16x8>), 2) \
+ V(sub, (BinaryFunc<Int16x8, Sub, Int16x8>), 2) \
+ V(subSaturate, (BinaryFunc<Int16x8, SubSaturate, Int16x8>), 2) \
+ V(shiftLeftByScalar, (BinaryScalar<Int16x8, ShiftLeft>), 2) \
+ V(shiftRightByScalar, (BinaryScalar<Int16x8, ShiftRightArithmetic>), 2) \
+ V(xor, (BinaryFunc<Int16x8, Xor, Int16x8>), 2)
+
+#define INT16X8_TERNARY_FUNCTION_LIST(V) \
+ V(replaceLane, (ReplaceLane<Int16x8>), 3) \
+ V(select, (Select<Int16x8, Bool16x8>), 3) \
+ V(store, (Store<Int16x8, 8>), 3)
+
+#define INT16X8_SHUFFLE_FUNCTION_LIST(V) \
+ V(swizzle, Swizzle<Int16x8>, 9) \
+ V(shuffle, Shuffle<Int16x8>, 10)
+
+#define INT16X8_FUNCTION_LIST(V) \
+ INT16X8_UNARY_FUNCTION_LIST(V) \
+ INT16X8_BINARY_FUNCTION_LIST(V) \
+ INT16X8_TERNARY_FUNCTION_LIST(V) \
+ INT16X8_SHUFFLE_FUNCTION_LIST(V)
+
+// Uint16x8.
+#define UINT16X8_UNARY_FUNCTION_LIST(V) \
+ V(check, (UnaryFunc<Uint16x8, Identity, Uint16x8>), 1) \
+ V(fromFloat32x4Bits, (FuncConvertBits<Float32x4, Uint16x8>), 1) \
+ V(fromFloat64x2Bits, (FuncConvertBits<Float64x2, Uint16x8>), 1) \
+ V(fromInt8x16Bits, (FuncConvertBits<Int8x16, Uint16x8>), 1) \
+ V(fromInt16x8Bits, (FuncConvertBits<Int16x8, Uint16x8>), 1) \
+ V(fromInt32x4Bits, (FuncConvertBits<Int32x4, Uint16x8>), 1) \
+ V(fromUint8x16Bits, (FuncConvertBits<Uint8x16, Uint16x8>), 1) \
+ V(fromUint32x4Bits, (FuncConvertBits<Uint32x4, Uint16x8>), 1) \
+ V(neg, (UnaryFunc<Uint16x8, Neg, Uint16x8>), 1) \
+ V(not, (UnaryFunc<Uint16x8, Not, Uint16x8>), 1) \
+ V(splat, (FuncSplat<Uint16x8>), 1)
+
+#define UINT16X8_BINARY_FUNCTION_LIST(V) \
+ V(add, (BinaryFunc<Uint16x8, Add, Uint16x8>), 2) \
+ V(addSaturate, (BinaryFunc<Uint16x8, AddSaturate, Uint16x8>), 2) \
+ V(and, (BinaryFunc<Uint16x8, And, Uint16x8>), 2) \
+ V(equal, (CompareFunc<Uint16x8, Equal, Bool16x8>), 2) \
+ V(extractLane, (ExtractLane<Uint16x8>), 2) \
+ V(greaterThan, (CompareFunc<Uint16x8, GreaterThan, Bool16x8>), 2) \
+ V(greaterThanOrEqual, (CompareFunc<Uint16x8, GreaterThanOrEqual, Bool16x8>), 2) \
+ V(lessThan, (CompareFunc<Uint16x8, LessThan, Bool16x8>), 2) \
+ V(lessThanOrEqual, (CompareFunc<Uint16x8, LessThanOrEqual, Bool16x8>), 2) \
+ V(load, (Load<Uint16x8, 8>), 2) \
+ V(mul, (BinaryFunc<Uint16x8, Mul, Uint16x8>), 2) \
+ V(notEqual, (CompareFunc<Uint16x8, NotEqual, Bool16x8>), 2) \
+ V(or, (BinaryFunc<Uint16x8, Or, Uint16x8>), 2) \
+ V(sub, (BinaryFunc<Uint16x8, Sub, Uint16x8>), 2) \
+ V(subSaturate, (BinaryFunc<Uint16x8, SubSaturate, Uint16x8>), 2) \
+ V(shiftLeftByScalar, (BinaryScalar<Uint16x8, ShiftLeft>), 2) \
+ V(shiftRightByScalar, (BinaryScalar<Uint16x8, ShiftRightLogical>), 2) \
+ V(xor, (BinaryFunc<Uint16x8, Xor, Uint16x8>), 2)
+
+#define UINT16X8_TERNARY_FUNCTION_LIST(V) \
+ V(replaceLane, (ReplaceLane<Uint16x8>), 3) \
+ V(select, (Select<Uint16x8, Bool16x8>), 3) \
+ V(store, (Store<Uint16x8, 8>), 3)
+
+#define UINT16X8_SHUFFLE_FUNCTION_LIST(V) \
+ V(swizzle, Swizzle<Uint16x8>, 9) \
+ V(shuffle, Shuffle<Uint16x8>, 10)
+
+#define UINT16X8_FUNCTION_LIST(V) \
+ UINT16X8_UNARY_FUNCTION_LIST(V) \
+ UINT16X8_BINARY_FUNCTION_LIST(V) \
+ UINT16X8_TERNARY_FUNCTION_LIST(V) \
+ UINT16X8_SHUFFLE_FUNCTION_LIST(V)
+
+// Int32x4.
+#define INT32X4_UNARY_FUNCTION_LIST(V) \
+ V(check, (UnaryFunc<Int32x4, Identity, Int32x4>), 1) \
+ V(fromFloat32x4, (FuncConvert<Float32x4, Int32x4>), 1) \
+ V(fromFloat32x4Bits, (FuncConvertBits<Float32x4, Int32x4>), 1) \
+ V(fromFloat64x2Bits, (FuncConvertBits<Float64x2, Int32x4>), 1) \
+ V(fromInt8x16Bits, (FuncConvertBits<Int8x16, Int32x4>), 1) \
+ V(fromInt16x8Bits, (FuncConvertBits<Int16x8, Int32x4>), 1) \
+ V(fromUint8x16Bits, (FuncConvertBits<Uint8x16, Int32x4>), 1) \
+ V(fromUint16x8Bits, (FuncConvertBits<Uint16x8, Int32x4>), 1) \
+ V(fromUint32x4Bits, (FuncConvertBits<Uint32x4, Int32x4>), 1) \
+ V(neg, (UnaryFunc<Int32x4, Neg, Int32x4>), 1) \
+ V(not, (UnaryFunc<Int32x4, Not, Int32x4>), 1) \
+ V(splat, (FuncSplat<Int32x4>), 0)
+
+#define INT32X4_BINARY_FUNCTION_LIST(V) \
+ V(add, (BinaryFunc<Int32x4, Add, Int32x4>), 2) \
+ V(and, (BinaryFunc<Int32x4, And, Int32x4>), 2) \
+ V(equal, (CompareFunc<Int32x4, Equal, Bool32x4>), 2) \
+ V(extractLane, (ExtractLane<Int32x4>), 2) \
+ V(greaterThan, (CompareFunc<Int32x4, GreaterThan, Bool32x4>), 2) \
+ V(greaterThanOrEqual, (CompareFunc<Int32x4, GreaterThanOrEqual, Bool32x4>), 2) \
+ V(lessThan, (CompareFunc<Int32x4, LessThan, Bool32x4>), 2) \
+ V(lessThanOrEqual, (CompareFunc<Int32x4, LessThanOrEqual, Bool32x4>), 2) \
+ V(load, (Load<Int32x4, 4>), 2) \
+ V(load3, (Load<Int32x4, 3>), 2) \
+ V(load2, (Load<Int32x4, 2>), 2) \
+ V(load1, (Load<Int32x4, 1>), 2) \
+ V(mul, (BinaryFunc<Int32x4, Mul, Int32x4>), 2) \
+ V(notEqual, (CompareFunc<Int32x4, NotEqual, Bool32x4>), 2) \
+ V(or, (BinaryFunc<Int32x4, Or, Int32x4>), 2) \
+ V(sub, (BinaryFunc<Int32x4, Sub, Int32x4>), 2) \
+ V(shiftLeftByScalar, (BinaryScalar<Int32x4, ShiftLeft>), 2) \
+ V(shiftRightByScalar, (BinaryScalar<Int32x4, ShiftRightArithmetic>), 2) \
+ V(xor, (BinaryFunc<Int32x4, Xor, Int32x4>), 2)
+
+#define INT32X4_TERNARY_FUNCTION_LIST(V) \
+ V(replaceLane, (ReplaceLane<Int32x4>), 3) \
+ V(select, (Select<Int32x4, Bool32x4>), 3) \
+ V(store, (Store<Int32x4, 4>), 3) \
+ V(store3, (Store<Int32x4, 3>), 3) \
+ V(store2, (Store<Int32x4, 2>), 3) \
+ V(store1, (Store<Int32x4, 1>), 3)
+
+#define INT32X4_SHUFFLE_FUNCTION_LIST(V) \
+ V(swizzle, Swizzle<Int32x4>, 5) \
+ V(shuffle, Shuffle<Int32x4>, 6)
+
+#define INT32X4_FUNCTION_LIST(V) \
+ INT32X4_UNARY_FUNCTION_LIST(V) \
+ INT32X4_BINARY_FUNCTION_LIST(V) \
+ INT32X4_TERNARY_FUNCTION_LIST(V) \
+ INT32X4_SHUFFLE_FUNCTION_LIST(V)
+
+// Uint32x4.
+#define UINT32X4_UNARY_FUNCTION_LIST(V) \
+ V(check, (UnaryFunc<Uint32x4, Identity, Uint32x4>), 1) \
+ V(fromFloat32x4, (FuncConvert<Float32x4, Uint32x4>), 1) \
+ V(fromFloat32x4Bits, (FuncConvertBits<Float32x4, Uint32x4>), 1) \
+ V(fromFloat64x2Bits, (FuncConvertBits<Float64x2, Uint32x4>), 1) \
+ V(fromInt8x16Bits, (FuncConvertBits<Int8x16, Uint32x4>), 1) \
+ V(fromInt16x8Bits, (FuncConvertBits<Int16x8, Uint32x4>), 1) \
+ V(fromInt32x4Bits, (FuncConvertBits<Int32x4, Uint32x4>), 1) \
+ V(fromUint8x16Bits, (FuncConvertBits<Uint8x16, Uint32x4>), 1) \
+ V(fromUint16x8Bits, (FuncConvertBits<Uint16x8, Uint32x4>), 1) \
+ V(neg, (UnaryFunc<Uint32x4, Neg, Uint32x4>), 1) \
+ V(not, (UnaryFunc<Uint32x4, Not, Uint32x4>), 1) \
+ V(splat, (FuncSplat<Uint32x4>), 0)
+
+#define UINT32X4_BINARY_FUNCTION_LIST(V) \
+ V(add, (BinaryFunc<Uint32x4, Add, Uint32x4>), 2) \
+ V(and, (BinaryFunc<Uint32x4, And, Uint32x4>), 2) \
+ V(equal, (CompareFunc<Uint32x4, Equal, Bool32x4>), 2) \
+ V(extractLane, (ExtractLane<Uint32x4>), 2) \
+ V(greaterThan, (CompareFunc<Uint32x4, GreaterThan, Bool32x4>), 2) \
+ V(greaterThanOrEqual, (CompareFunc<Uint32x4, GreaterThanOrEqual, Bool32x4>), 2) \
+ V(lessThan, (CompareFunc<Uint32x4, LessThan, Bool32x4>), 2) \
+ V(lessThanOrEqual, (CompareFunc<Uint32x4, LessThanOrEqual, Bool32x4>), 2) \
+ V(load, (Load<Uint32x4, 4>), 2) \
+ V(load3, (Load<Uint32x4, 3>), 2) \
+ V(load2, (Load<Uint32x4, 2>), 2) \
+ V(load1, (Load<Uint32x4, 1>), 2) \
+ V(mul, (BinaryFunc<Uint32x4, Mul, Uint32x4>), 2) \
+ V(notEqual, (CompareFunc<Uint32x4, NotEqual, Bool32x4>), 2) \
+ V(or, (BinaryFunc<Uint32x4, Or, Uint32x4>), 2) \
+ V(sub, (BinaryFunc<Uint32x4, Sub, Uint32x4>), 2) \
+ V(shiftLeftByScalar, (BinaryScalar<Uint32x4, ShiftLeft>), 2) \
+ V(shiftRightByScalar, (BinaryScalar<Uint32x4, ShiftRightLogical>), 2) \
+ V(xor, (BinaryFunc<Uint32x4, Xor, Uint32x4>), 2)
+
+#define UINT32X4_TERNARY_FUNCTION_LIST(V) \
+ V(replaceLane, (ReplaceLane<Uint32x4>), 3) \
+ V(select, (Select<Uint32x4, Bool32x4>), 3) \
+ V(store, (Store<Uint32x4, 4>), 3) \
+ V(store3, (Store<Uint32x4, 3>), 3) \
+ V(store2, (Store<Uint32x4, 2>), 3) \
+ V(store1, (Store<Uint32x4, 1>), 3)
+
+#define UINT32X4_SHUFFLE_FUNCTION_LIST(V) \
+ V(swizzle, Swizzle<Uint32x4>, 5) \
+ V(shuffle, Shuffle<Uint32x4>, 6)
+
+#define UINT32X4_FUNCTION_LIST(V) \
+ UINT32X4_UNARY_FUNCTION_LIST(V) \
+ UINT32X4_BINARY_FUNCTION_LIST(V) \
+ UINT32X4_TERNARY_FUNCTION_LIST(V) \
+ UINT32X4_SHUFFLE_FUNCTION_LIST(V)
+
+/*
+ * The FOREACH macros below partition all of the SIMD operations into disjoint
+ * sets.
+ */
+
+// Operations available on all SIMD types. Mixed arity.
+#define FOREACH_COMMON_SIMD_OP(_) \
+ _(extractLane) \
+ _(replaceLane) \
+ _(check) \
+ _(splat)
+
+// Lanewise operations available on numeric SIMD types.
+// Include lane-wise select here since it is not arithmetic and defined on
+// numeric types too.
+#define FOREACH_LANE_SIMD_OP(_) \
+ _(select) \
+ _(swizzle) \
+ _(shuffle)
+
+// Memory operations available on numeric SIMD types.
+#define FOREACH_MEMORY_SIMD_OP(_) \
+ _(load) \
+ _(store)
+
+// Memory operations available on numeric X4 SIMD types.
+#define FOREACH_MEMORY_X4_SIMD_OP(_) \
+ _(load1) \
+ _(load2) \
+ _(load3) \
+ _(store1) \
+ _(store2) \
+ _(store3)
+
+// Unary operations on Bool vectors.
+#define FOREACH_BOOL_SIMD_UNOP(_) \
+ _(allTrue) \
+ _(anyTrue)
+
+// Unary bitwise SIMD operators defined on all integer and boolean SIMD types.
+#define FOREACH_BITWISE_SIMD_UNOP(_) \
+ _(not)
+
+// Binary bitwise SIMD operators defined on all integer and boolean SIMD types.
+#define FOREACH_BITWISE_SIMD_BINOP(_) \
+ _(and) \
+ _(or) \
+ _(xor)
+
+// Bitwise shifts defined on integer SIMD types.
+#define FOREACH_SHIFT_SIMD_OP(_) \
+ _(shiftLeftByScalar) \
+ _(shiftRightByScalar)
+
+// Unary arithmetic operators defined on numeric SIMD types.
+#define FOREACH_NUMERIC_SIMD_UNOP(_) \
+ _(neg)
+
+// Binary arithmetic operators defined on numeric SIMD types.
+#define FOREACH_NUMERIC_SIMD_BINOP(_) \
+ _(add) \
+ _(sub) \
+ _(mul)
+
+// Unary arithmetic operators defined on floating point SIMD types.
+#define FOREACH_FLOAT_SIMD_UNOP(_) \
+ _(abs) \
+ _(sqrt) \
+ _(reciprocalApproximation) \
+ _(reciprocalSqrtApproximation)
+
+// Binary arithmetic operators defined on floating point SIMD types.
+#define FOREACH_FLOAT_SIMD_BINOP(_) \
+ _(div) \
+ _(max) \
+ _(min) \
+ _(maxNum) \
+ _(minNum)
+
+// Binary operations on small integer (< 32 bits) vectors.
+#define FOREACH_SMINT_SIMD_BINOP(_) \
+ _(addSaturate) \
+ _(subSaturate)
+
+// Comparison operators defined on numeric SIMD types.
+#define FOREACH_COMP_SIMD_OP(_) \
+ _(lessThan) \
+ _(lessThanOrEqual) \
+ _(equal) \
+ _(notEqual) \
+ _(greaterThan) \
+ _(greaterThanOrEqual)
+
+/*
+ * All SIMD operations, excluding casts.
+ */
+#define FORALL_SIMD_NONCAST_OP(_) \
+ FOREACH_COMMON_SIMD_OP(_) \
+ FOREACH_LANE_SIMD_OP(_) \
+ FOREACH_MEMORY_SIMD_OP(_) \
+ FOREACH_MEMORY_X4_SIMD_OP(_) \
+ FOREACH_BOOL_SIMD_UNOP(_) \
+ FOREACH_BITWISE_SIMD_UNOP(_) \
+ FOREACH_BITWISE_SIMD_BINOP(_) \
+ FOREACH_SHIFT_SIMD_OP(_) \
+ FOREACH_NUMERIC_SIMD_UNOP(_) \
+ FOREACH_NUMERIC_SIMD_BINOP(_) \
+ FOREACH_FLOAT_SIMD_UNOP(_) \
+ FOREACH_FLOAT_SIMD_BINOP(_) \
+ FOREACH_SMINT_SIMD_BINOP(_) \
+ FOREACH_COMP_SIMD_OP(_)
+
+/*
+ * All operations on integer SIMD types, excluding casts and
+ * FOREACH_MEMORY_X4_OP.
+ */
+#define FORALL_INT_SIMD_OP(_) \
+ FOREACH_COMMON_SIMD_OP(_) \
+ FOREACH_LANE_SIMD_OP(_) \
+ FOREACH_MEMORY_SIMD_OP(_) \
+ FOREACH_BITWISE_SIMD_UNOP(_) \
+ FOREACH_BITWISE_SIMD_BINOP(_) \
+ FOREACH_SHIFT_SIMD_OP(_) \
+ FOREACH_NUMERIC_SIMD_UNOP(_) \
+ FOREACH_NUMERIC_SIMD_BINOP(_) \
+ FOREACH_COMP_SIMD_OP(_)
+
+/*
+ * All operations on floating point SIMD types, excluding casts and
+ * FOREACH_MEMORY_X4_OP.
+ */
+#define FORALL_FLOAT_SIMD_OP(_) \
+ FOREACH_COMMON_SIMD_OP(_) \
+ FOREACH_LANE_SIMD_OP(_) \
+ FOREACH_MEMORY_SIMD_OP(_) \
+ FOREACH_NUMERIC_SIMD_UNOP(_) \
+ FOREACH_NUMERIC_SIMD_BINOP(_) \
+ FOREACH_FLOAT_SIMD_UNOP(_) \
+ FOREACH_FLOAT_SIMD_BINOP(_) \
+ FOREACH_COMP_SIMD_OP(_)
+
+/*
+ * All operations on Bool SIMD types.
+ *
+ * These types don't have casts, so no need to specialize.
+ */
+#define FORALL_BOOL_SIMD_OP(_) \
+ FOREACH_COMMON_SIMD_OP(_) \
+ FOREACH_BOOL_SIMD_UNOP(_) \
+ FOREACH_BITWISE_SIMD_UNOP(_) \
+ FOREACH_BITWISE_SIMD_BINOP(_)
+
+/*
+ * The sets of cast operations are listed per type below.
+ *
+ * These sets are not disjoint.
+ */
+
+#define FOREACH_INT8X16_SIMD_CAST(_) \
+ _(fromFloat32x4Bits) \
+ _(fromFloat64x2Bits) \
+ _(fromInt16x8Bits) \
+ _(fromInt32x4Bits)
+
+#define FOREACH_INT16X8_SIMD_CAST(_) \
+ _(fromFloat32x4Bits) \
+ _(fromFloat64x2Bits) \
+ _(fromInt8x16Bits) \
+ _(fromInt32x4Bits)
+
+#define FOREACH_INT32X4_SIMD_CAST(_) \
+ _(fromFloat32x4) \
+ _(fromFloat32x4Bits) \
+ _(fromFloat64x2Bits) \
+ _(fromInt8x16Bits) \
+ _(fromInt16x8Bits)
+
+#define FOREACH_FLOAT32X4_SIMD_CAST(_)\
+ _(fromFloat64x2Bits) \
+ _(fromInt8x16Bits) \
+ _(fromInt16x8Bits) \
+ _(fromInt32x4) \
+ _(fromInt32x4Bits)
+
+#define FOREACH_FLOAT64X2_SIMD_CAST(_)\
+ _(fromFloat32x4Bits) \
+ _(fromInt8x16Bits) \
+ _(fromInt16x8Bits) \
+ _(fromInt32x4Bits)
+
+// All operations on Int32x4.
+#define FORALL_INT32X4_SIMD_OP(_) \
+ FORALL_INT_SIMD_OP(_) \
+ FOREACH_MEMORY_X4_SIMD_OP(_) \
+ FOREACH_INT32X4_SIMD_CAST(_)
+
+// All operations on Float32X4
+#define FORALL_FLOAT32X4_SIMD_OP(_) \
+ FORALL_FLOAT_SIMD_OP(_) \
+ FOREACH_MEMORY_X4_SIMD_OP(_) \
+ FOREACH_FLOAT32X4_SIMD_CAST(_)
+
+/*
+ * All SIMD operations assuming only 32x4 types exist.
+ * This is used in the current asm.js impl.
+ */
+#define FORALL_SIMD_ASMJS_OP(_) \
+ FORALL_SIMD_NONCAST_OP(_) \
+ _(fromFloat32x4) \
+ _(fromFloat32x4Bits) \
+ _(fromInt8x16Bits) \
+ _(fromInt16x8Bits) \
+ _(fromInt32x4) \
+ _(fromInt32x4Bits) \
+ _(fromUint8x16Bits) \
+ _(fromUint16x8Bits) \
+ _(fromUint32x4) \
+ _(fromUint32x4Bits)
+
+// All operations on Int8x16 or Uint8x16 in the asm.js world.
+// Note: this does not include conversions and casts to/from Uint8x16 because
+// this list is shared between Int8x16 and Uint8x16.
+#define FORALL_INT8X16_ASMJS_OP(_) \
+ FORALL_INT_SIMD_OP(_) \
+ FOREACH_SMINT_SIMD_BINOP(_) \
+ _(fromInt16x8Bits) \
+ _(fromInt32x4Bits) \
+ _(fromFloat32x4Bits)
+
+// All operations on Int16x8 or Uint16x8 in the asm.js world.
+// Note: this does not include conversions and casts to/from Uint16x8 because
+// this list is shared between Int16x8 and Uint16x8.
+#define FORALL_INT16X8_ASMJS_OP(_) \
+ FORALL_INT_SIMD_OP(_) \
+ FOREACH_SMINT_SIMD_BINOP(_) \
+ _(fromInt8x16Bits) \
+ _(fromInt32x4Bits) \
+ _(fromFloat32x4Bits)
+
+// All operations on Int32x4 or Uint32x4 in the asm.js world.
+// Note: this does not include conversions and casts to/from Uint32x4 because
+// this list is shared between Int32x4 and Uint32x4.
+#define FORALL_INT32X4_ASMJS_OP(_) \
+ FORALL_INT_SIMD_OP(_) \
+ FOREACH_MEMORY_X4_SIMD_OP(_) \
+ _(fromInt8x16Bits) \
+ _(fromInt16x8Bits) \
+ _(fromFloat32x4) \
+ _(fromFloat32x4Bits)
+
+// All operations on Float32X4 in the asm.js world.
+#define FORALL_FLOAT32X4_ASMJS_OP(_) \
+ FORALL_FLOAT_SIMD_OP(_) \
+ FOREACH_MEMORY_X4_SIMD_OP(_) \
+ _(fromInt8x16Bits) \
+ _(fromInt16x8Bits) \
+ _(fromInt32x4Bits) \
+ _(fromInt32x4) \
+ _(fromUint32x4)
+
+namespace js {
+
+// Complete set of SIMD types.
+// It must be kept in sync with the enumeration of values in
+// TypedObjectConstants.h; in particular we need to ensure that Count is
+// appropriately set with respect to the number of actual types.
+enum class SimdType {
+ Int8x16 = JS_SIMDTYPEREPR_INT8X16,
+ Int16x8 = JS_SIMDTYPEREPR_INT16X8,
+ Int32x4 = JS_SIMDTYPEREPR_INT32X4,
+ Uint8x16 = JS_SIMDTYPEREPR_UINT8X16,
+ Uint16x8 = JS_SIMDTYPEREPR_UINT16X8,
+ Uint32x4 = JS_SIMDTYPEREPR_UINT32X4,
+ Float32x4 = JS_SIMDTYPEREPR_FLOAT32X4,
+ Float64x2 = JS_SIMDTYPEREPR_FLOAT64X2,
+ Bool8x16 = JS_SIMDTYPEREPR_BOOL8X16,
+ Bool16x8 = JS_SIMDTYPEREPR_BOOL16X8,
+ Bool32x4 = JS_SIMDTYPEREPR_BOOL32X4,
+ Bool64x2 = JS_SIMDTYPEREPR_BOOL64X2,
+ Count
+};
+
+// The integer SIMD types have a lot of operations that do the exact same thing
+// for signed and unsigned integer types. Sometimes it is simpler to treat
+// signed and unsigned integer SIMD types as the same type, using a SimdSign to
+// distinguish the few cases where there is a difference.
+enum class SimdSign {
+ // Signedness is not applicable to this type. (i.e., Float or Bool).
+ NotApplicable,
+ // Treat as an unsigned integer with a range 0 .. 2^N-1.
+ Unsigned,
+ // Treat as a signed integer in two's complement encoding.
+ Signed,
+};
+
+// Get the signedness of a SIMD type.
+inline SimdSign
+GetSimdSign(SimdType t)
+{
+ switch(t) {
+ case SimdType::Int8x16:
+ case SimdType::Int16x8:
+ case SimdType::Int32x4:
+ return SimdSign::Signed;
+
+ case SimdType::Uint8x16:
+ case SimdType::Uint16x8:
+ case SimdType::Uint32x4:
+ return SimdSign::Unsigned;
+
+ default:
+ return SimdSign::NotApplicable;
+ }
+}
+
+inline bool
+IsSignedIntSimdType(SimdType type)
+{
+ return GetSimdSign(type) == SimdSign::Signed;
+}
+
+// Get the boolean SIMD type with the same shape as t.
+//
+// This is the result type of a comparison operation, and it can also be used to
+// identify the geometry of a SIMD type.
+inline SimdType
+GetBooleanSimdType(SimdType t)
+{
+ switch(t) {
+ case SimdType::Int8x16:
+ case SimdType::Uint8x16:
+ case SimdType::Bool8x16:
+ return SimdType::Bool8x16;
+
+ case SimdType::Int16x8:
+ case SimdType::Uint16x8:
+ case SimdType::Bool16x8:
+ return SimdType::Bool16x8;
+
+ case SimdType::Int32x4:
+ case SimdType::Uint32x4:
+ case SimdType::Float32x4:
+ case SimdType::Bool32x4:
+ return SimdType::Bool32x4;
+
+ case SimdType::Float64x2:
+ case SimdType::Bool64x2:
+ return SimdType::Bool64x2;
+
+ case SimdType::Count:
+ break;
+ }
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad SIMD type");
+}
+
+// Get the number of lanes in a SIMD type.
+inline unsigned
+GetSimdLanes(SimdType t)
+{
+ switch(t) {
+ case SimdType::Int8x16:
+ case SimdType::Uint8x16:
+ case SimdType::Bool8x16:
+ return 16;
+
+ case SimdType::Int16x8:
+ case SimdType::Uint16x8:
+ case SimdType::Bool16x8:
+ return 8;
+
+ case SimdType::Int32x4:
+ case SimdType::Uint32x4:
+ case SimdType::Float32x4:
+ case SimdType::Bool32x4:
+ return 4;
+
+ case SimdType::Float64x2:
+ case SimdType::Bool64x2:
+ return 2;
+
+ case SimdType::Count:
+ break;
+ }
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad SIMD type");
+}
+
+// Complete set of SIMD operations.
+//
+// No SIMD types implement all of these operations.
+//
+// C++ defines keywords and/or/xor/not, so prepend Fn_ to all named functions to
+// avoid clashes.
+//
+// Note: because of a gcc < v4.8's compiler bug, uint8_t can't be used as the
+// storage class here. See bug 1243810. See also
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64037 .
+enum class SimdOperation {
+ // The constructor call. No Fn_ prefix here.
+ Constructor,
+
+ // All the operations, except for casts.
+#define DEFOP(x) Fn_##x,
+ FORALL_SIMD_NONCAST_OP(DEFOP)
+#undef DEFOP
+
+ // Int <-> Float conversions.
+ Fn_fromInt32x4,
+ Fn_fromUint32x4,
+ Fn_fromFloat32x4,
+
+ // Bitcasts. One for each type with a memory representation.
+ Fn_fromInt8x16Bits,
+ Fn_fromInt16x8Bits,
+ Fn_fromInt32x4Bits,
+ Fn_fromUint8x16Bits,
+ Fn_fromUint16x8Bits,
+ Fn_fromUint32x4Bits,
+ Fn_fromFloat32x4Bits,
+ Fn_fromFloat64x2Bits,
+
+ Last = Fn_fromFloat64x2Bits
+};
+
+// These classes implement the concept containing the following constraints:
+// - requires typename Elem: this is the scalar lane type, stored in each lane
+// of the SIMD vector.
+// - requires static const unsigned lanes: this is the number of lanes (length)
+// of the SIMD vector.
+// - requires static const SimdType type: this is the SimdType enum value
+// corresponding to the SIMD type.
+// - requires static bool Cast(JSContext*, JS::HandleValue, Elem*): casts a
+// given Value to the current scalar lane type and saves it in the Elem
+// out-param.
+// - requires static Value ToValue(Elem): returns a Value of the right type
+// containing the given value.
+//
+// This concept is used in the templates above to define the functions
+// associated to a given type and in their implementations, to avoid code
+// redundancy.
+
+struct Float32x4 {
+ typedef float Elem;
+ static const unsigned lanes = 4;
+ static const SimdType type = SimdType::Float32x4;
+ static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) {
+ double d;
+ if (!ToNumber(cx, v, &d))
+ return false;
+ *out = float(d);
+ return true;
+ }
+ static Value ToValue(Elem value) {
+ return DoubleValue(JS::CanonicalizeNaN(value));
+ }
+};
+
+struct Float64x2 {
+ typedef double Elem;
+ static const unsigned lanes = 2;
+ static const SimdType type = SimdType::Float64x2;
+ static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) {
+ return ToNumber(cx, v, out);
+ }
+ static Value ToValue(Elem value) {
+ return DoubleValue(JS::CanonicalizeNaN(value));
+ }
+};
+
+struct Int8x16 {
+ typedef int8_t Elem;
+ static const unsigned lanes = 16;
+ static const SimdType type = SimdType::Int8x16;
+ static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) {
+ return ToInt8(cx, v, out);
+ }
+ static Value ToValue(Elem value) {
+ return NumberValue(value);
+ }
+};
+
+struct Int16x8 {
+ typedef int16_t Elem;
+ static const unsigned lanes = 8;
+ static const SimdType type = SimdType::Int16x8;
+ static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) {
+ return ToInt16(cx, v, out);
+ }
+ static Value ToValue(Elem value) {
+ return NumberValue(value);
+ }
+};
+
+struct Int32x4 {
+ typedef int32_t Elem;
+ static const unsigned lanes = 4;
+ static const SimdType type = SimdType::Int32x4;
+ static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) {
+ return ToInt32(cx, v, out);
+ }
+ static Value ToValue(Elem value) {
+ return NumberValue(value);
+ }
+};
+
+struct Uint8x16 {
+ typedef uint8_t Elem;
+ static const unsigned lanes = 16;
+ static const SimdType type = SimdType::Uint8x16;
+ static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) {
+ return ToUint8(cx, v, out);
+ }
+ static Value ToValue(Elem value) {
+ return NumberValue(value);
+ }
+};
+
+struct Uint16x8 {
+ typedef uint16_t Elem;
+ static const unsigned lanes = 8;
+ static const SimdType type = SimdType::Uint16x8;
+ static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) {
+ return ToUint16(cx, v, out);
+ }
+ static Value ToValue(Elem value) {
+ return NumberValue(value);
+ }
+};
+
+struct Uint32x4 {
+ typedef uint32_t Elem;
+ static const unsigned lanes = 4;
+ static const SimdType type = SimdType::Uint32x4;
+ static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) {
+ return ToUint32(cx, v, out);
+ }
+ static Value ToValue(Elem value) {
+ return NumberValue(value);
+ }
+};
+
+struct Bool8x16 {
+ typedef int8_t Elem;
+ static const unsigned lanes = 16;
+ static const SimdType type = SimdType::Bool8x16;
+ static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) {
+ *out = ToBoolean(v) ? -1 : 0;
+ return true;
+ }
+ static Value ToValue(Elem value) {
+ return BooleanValue(value);
+ }
+};
+
+struct Bool16x8 {
+ typedef int16_t Elem;
+ static const unsigned lanes = 8;
+ static const SimdType type = SimdType::Bool16x8;
+ static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) {
+ *out = ToBoolean(v) ? -1 : 0;
+ return true;
+ }
+ static Value ToValue(Elem value) {
+ return BooleanValue(value);
+ }
+};
+
+struct Bool32x4 {
+ typedef int32_t Elem;
+ static const unsigned lanes = 4;
+ static const SimdType type = SimdType::Bool32x4;
+ static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) {
+ *out = ToBoolean(v) ? -1 : 0;
+ return true;
+ }
+ static Value ToValue(Elem value) {
+ return BooleanValue(value);
+ }
+};
+
+struct Bool64x2 {
+ typedef int64_t Elem;
+ static const unsigned lanes = 2;
+ static const SimdType type = SimdType::Bool64x2;
+ static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) {
+ *out = ToBoolean(v) ? -1 : 0;
+ return true;
+ }
+ static Value ToValue(Elem value) {
+ return BooleanValue(value);
+ }
+};
+
+// Get the well known name of the SIMD.* object corresponding to type.
+PropertyName* SimdTypeToName(const JSAtomState& atoms, SimdType type);
+
+// Check if name is the well known name of a SIMD type.
+// Returns true and sets *type iff name is known.
+bool IsSimdTypeName(const JSAtomState& atoms, const PropertyName* name, SimdType* type);
+
+const char* SimdTypeToString(SimdType type);
+
+template<typename V>
+JSObject* CreateSimd(JSContext* cx, const typename V::Elem* data);
+
+template<typename V>
+bool IsVectorObject(HandleValue v);
+
+template<typename V>
+MOZ_MUST_USE bool ToSimdConstant(JSContext* cx, HandleValue v, jit::SimdConstant* out);
+
+JSObject*
+InitSimdClass(JSContext* cx, HandleObject obj);
+
+namespace jit {
+
+extern const JSJitInfo JitInfo_SimdInt32x4_extractLane;
+extern const JSJitInfo JitInfo_SimdFloat32x4_extractLane;
+
+} // namespace jit
+
+#define DECLARE_SIMD_FLOAT32X4_FUNCTION(Name, Func, Operands) \
+extern MOZ_MUST_USE bool \
+simd_float32x4_##Name(JSContext* cx, unsigned argc, Value* vp);
+FLOAT32X4_FUNCTION_LIST(DECLARE_SIMD_FLOAT32X4_FUNCTION)
+#undef DECLARE_SIMD_FLOAT32X4_FUNCTION
+
+#define DECLARE_SIMD_FLOAT64X2_FUNCTION(Name, Func, Operands) \
+extern MOZ_MUST_USE bool \
+simd_float64x2_##Name(JSContext* cx, unsigned argc, Value* vp);
+FLOAT64X2_FUNCTION_LIST(DECLARE_SIMD_FLOAT64X2_FUNCTION)
+#undef DECLARE_SIMD_FLOAT64X2_FUNCTION
+
+#define DECLARE_SIMD_INT8X16_FUNCTION(Name, Func, Operands) \
+extern MOZ_MUST_USE bool \
+simd_int8x16_##Name(JSContext* cx, unsigned argc, Value* vp);
+INT8X16_FUNCTION_LIST(DECLARE_SIMD_INT8X16_FUNCTION)
+#undef DECLARE_SIMD_INT8X16_FUNCTION
+
+#define DECLARE_SIMD_INT16X8_FUNCTION(Name, Func, Operands) \
+extern MOZ_MUST_USE bool \
+simd_int16x8_##Name(JSContext* cx, unsigned argc, Value* vp);
+INT16X8_FUNCTION_LIST(DECLARE_SIMD_INT16X8_FUNCTION)
+#undef DECLARE_SIMD_INT16X8_FUNCTION
+
+#define DECLARE_SIMD_INT32X4_FUNCTION(Name, Func, Operands) \
+extern MOZ_MUST_USE bool \
+simd_int32x4_##Name(JSContext* cx, unsigned argc, Value* vp);
+INT32X4_FUNCTION_LIST(DECLARE_SIMD_INT32X4_FUNCTION)
+#undef DECLARE_SIMD_INT32X4_FUNCTION
+
+#define DECLARE_SIMD_UINT8X16_FUNCTION(Name, Func, Operands) \
+extern MOZ_MUST_USE bool \
+simd_uint8x16_##Name(JSContext* cx, unsigned argc, Value* vp);
+UINT8X16_FUNCTION_LIST(DECLARE_SIMD_UINT8X16_FUNCTION)
+#undef DECLARE_SIMD_UINT8X16_FUNCTION
+
+#define DECLARE_SIMD_UINT16X8_FUNCTION(Name, Func, Operands) \
+extern MOZ_MUST_USE bool \
+simd_uint16x8_##Name(JSContext* cx, unsigned argc, Value* vp);
+UINT16X8_FUNCTION_LIST(DECLARE_SIMD_UINT16X8_FUNCTION)
+#undef DECLARE_SIMD_UINT16X8_FUNCTION
+
+#define DECLARE_SIMD_UINT32X4_FUNCTION(Name, Func, Operands) \
+extern MOZ_MUST_USE bool \
+simd_uint32x4_##Name(JSContext* cx, unsigned argc, Value* vp);
+UINT32X4_FUNCTION_LIST(DECLARE_SIMD_UINT32X4_FUNCTION)
+#undef DECLARE_SIMD_UINT32X4_FUNCTION
+
+#define DECLARE_SIMD_BOOL8X16_FUNCTION(Name, Func, Operands) \
+extern MOZ_MUST_USE bool \
+simd_bool8x16_##Name(JSContext* cx, unsigned argc, Value* vp);
+BOOL8X16_FUNCTION_LIST(DECLARE_SIMD_BOOL8X16_FUNCTION)
+#undef DECLARE_SIMD_BOOL8X16_FUNCTION
+
+#define DECLARE_SIMD_BOOL16X8_FUNCTION(Name, Func, Operands) \
+extern MOZ_MUST_USE bool \
+simd_bool16x8_##Name(JSContext* cx, unsigned argc, Value* vp);
+BOOL16X8_FUNCTION_LIST(DECLARE_SIMD_BOOL16X8_FUNCTION)
+#undef DECLARE_SIMD_BOOL16X8_FUNCTION
+
+#define DECLARE_SIMD_BOOL32X4_FUNCTION(Name, Func, Operands) \
+extern MOZ_MUST_USE bool \
+simd_bool32x4_##Name(JSContext* cx, unsigned argc, Value* vp);
+BOOL32X4_FUNCTION_LIST(DECLARE_SIMD_BOOL32X4_FUNCTION)
+#undef DECLARE_SIMD_BOOL32X4_FUNCTION
+
+#define DECLARE_SIMD_BOOL64X2_FUNCTION(Name, Func, Operands) \
+extern MOZ_MUST_USE bool \
+simd_bool64x2_##Name(JSContext* cx, unsigned argc, Value* vp);
+BOOL64X2_FUNCTION_LIST(DECLARE_SIMD_BOOL64X2_FUNCTION)
+#undef DECLARE_SIMD_BOOL64X2_FUNCTION
+
+} /* namespace js */
+
+#endif /* builtin_SIMD_h */
diff --git a/js/src/builtin/SelfHostingDefines.h b/js/src/builtin/SelfHostingDefines.h
new file mode 100644
index 000000000..b57c17269
--- /dev/null
+++ b/js/src/builtin/SelfHostingDefines.h
@@ -0,0 +1,103 @@
+/* -*- 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/. */
+
+// Specialized .h file to be used by both JS and C++ code.
+
+#ifndef builtin_SelfHostingDefines_h
+#define builtin_SelfHostingDefines_h
+
+// Utility macros.
+#define TO_INT32(x) ((x) | 0)
+#define TO_UINT32(x) ((x) >>> 0)
+#define IS_UINT32(x) ((x) >>> 0 === (x))
+#define MAX_NUMERIC_INDEX 0x1fffffffffffff // == Math.pow(2, 53) - 1
+
+// Unforgeable version of Function.prototype.apply.
+#define FUN_APPLY(FUN, RECEIVER, ARGS) \
+ callFunction(std_Function_apply, FUN, RECEIVER, ARGS)
+
+// NB: keep this in sync with the copy in vm/ArgumentsObject.h.
+#define MAX_ARGS_LENGTH (500 * 1000)
+
+// Spread non-empty argument list of up to 15 elements.
+#define SPREAD(v, n) SPREAD_##n(v)
+#define SPREAD_1(v) v[0]
+#define SPREAD_2(v) SPREAD_1(v), v[1]
+#define SPREAD_3(v) SPREAD_2(v), v[2]
+#define SPREAD_4(v) SPREAD_3(v), v[3]
+#define SPREAD_5(v) SPREAD_4(v), v[4]
+#define SPREAD_6(v) SPREAD_5(v), v[5]
+#define SPREAD_7(v) SPREAD_6(v), v[6]
+#define SPREAD_8(v) SPREAD_7(v), v[7]
+#define SPREAD_9(v) SPREAD_8(v), v[8]
+#define SPREAD_10(v) SPREAD_9(v), v[9]
+#define SPREAD_11(v) SPREAD_10(v), v[10]
+#define SPREAD_12(v) SPREAD_11(v), v[11]
+#define SPREAD_13(v) SPREAD_12(v), v[12]
+#define SPREAD_14(v) SPREAD_13(v), v[13]
+#define SPREAD_15(v) SPREAD_14(v), v[14]
+
+// Property descriptor attributes.
+#define ATTR_ENUMERABLE 0x01
+#define ATTR_CONFIGURABLE 0x02
+#define ATTR_WRITABLE 0x04
+
+#define ATTR_NONENUMERABLE 0x08
+#define ATTR_NONCONFIGURABLE 0x10
+#define ATTR_NONWRITABLE 0x20
+
+// The extended slot in which the self-hosted name for self-hosted builtins is
+// stored.
+#define LAZY_FUNCTION_NAME_SLOT 0
+
+// The extended slot which contains a boolean value that indicates whether
+// that the canonical name of the self-hosted builtins is set in self-hosted
+// global. This slot is used only in debug build.
+#define HAS_SELFHOSTED_CANONICAL_NAME_SLOT 0
+
+// Stores the length for bound functions, so the .length property doesn't need
+// to be resolved eagerly.
+#define BOUND_FUN_LENGTH_SLOT 1
+
+// Stores the private WeakMap slot used for WeakSets
+#define WEAKSET_MAP_SLOT 0
+
+#define ITERATOR_SLOT_TARGET 0
+// Used for collection iterators.
+#define ITERATOR_SLOT_RANGE 1
+// Used for list, i.e. Array and String, iterators.
+#define ITERATOR_SLOT_NEXT_INDEX 1
+#define ITERATOR_SLOT_ITEM_KIND 2
+// Used for ListIterator.
+#define ITERATOR_SLOT_NEXT_METHOD 2
+
+#define ITEM_KIND_KEY 0
+#define ITEM_KIND_VALUE 1
+#define ITEM_KIND_KEY_AND_VALUE 2
+
+// NB: keep these in sync with the copy in jsfriendapi.h.
+#define JSITER_OWNONLY 0x8 /* iterate over obj's own properties only */
+#define JSITER_HIDDEN 0x10 /* also enumerate non-enumerable properties */
+#define JSITER_SYMBOLS 0x20 /* also include symbol property keys */
+#define JSITER_SYMBOLSONLY 0x40 /* exclude string property keys */
+
+
+#define REGEXP_FLAGS_SLOT 2
+
+#define REGEXP_IGNORECASE_FLAG 0x01
+#define REGEXP_GLOBAL_FLAG 0x02
+#define REGEXP_MULTILINE_FLAG 0x04
+#define REGEXP_STICKY_FLAG 0x08
+#define REGEXP_UNICODE_FLAG 0x10
+
+#define MODULE_OBJECT_ENVIRONMENT_SLOT 2
+
+#define MODULE_STATE_FAILED 0
+#define MODULE_STATE_PARSED 1
+#define MODULE_STATE_INSTANTIATED 2
+#define MODULE_STATE_EVALUATED 3
+
+#endif
diff --git a/js/src/builtin/Set.js b/js/src/builtin/Set.js
new file mode 100644
index 000000000..accc70120
--- /dev/null
+++ b/js/src/builtin/Set.js
@@ -0,0 +1,130 @@
+/* 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/. */
+
+// ES2017 draft rev 0e10c9f29fca1385980c08a7d5e7bb3eb775e2e4
+// 23.2.1.1 Set, steps 6-8
+function SetConstructorInit(iterable) {
+ var set = this;
+
+ // Step 6.a.
+ var adder = set.add;
+
+ // Step 6.b.
+ if (!IsCallable(adder))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, typeof adder);
+
+ // Step 6.c.
+ var iterFn = iterable[std_iterator];
+ if (!IsCallable(iterFn))
+ ThrowTypeError(JSMSG_NOT_ITERABLE, DecompileArg(0, iterable));
+
+ var iter = callContentFunction(iterFn, iterable);
+ if (!IsObject(iter))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof iter);
+
+ // Step 7 (not applicable).
+
+ // Step 8.
+ while (true) {
+ // Step 8.a.
+ var next = callContentFunction(iter.next, iter);
+ if (!IsObject(next))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof next);
+
+ // Step 8.b.
+ if (next.done)
+ return;
+
+ // Step 8.c.
+ var nextValue = next.value;
+
+ // Steps 8.d-e.
+ callContentFunction(adder, set, nextValue);
+ }
+}
+
+/* ES6 20121122 draft 15.16.4.6. */
+function SetForEach(callbackfn, thisArg = undefined) {
+ /* Step 1-2. */
+ var S = this;
+ if (!IsObject(S))
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "Set", "forEach", typeof S);
+
+ /* Step 3-4. */
+ try {
+ callFunction(std_Set_has, S);
+ } catch (e) {
+ // has will throw on non-Set objects, throw our own error in that case.
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "Set", "forEach", typeof S);
+ }
+
+ /* Step 5-6. */
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ /* Step 7-8. */
+ var values = callFunction(std_Set_iterator, S);
+ while (true) {
+ var result = callFunction(SetIteratorNext, values);
+ if (result.done)
+ break;
+ var value = result.value;
+ callContentFunction(callbackfn, thisArg, value, value, S);
+ }
+}
+
+// ES6 final draft 23.2.2.2.
+function SetSpecies() {
+ // Step 1.
+ return this;
+}
+_SetCanonicalName(SetSpecies, "get [Symbol.species]");
+
+
+var setIteratorTemp = { setIterationResult : null };
+
+function SetIteratorNext() {
+ // Step 1.
+ var O = this;
+
+ // Steps 2-3.
+ if (!IsObject(O) || !IsSetIterator(O))
+ return callFunction(CallSetIteratorMethodIfWrapped, O, "SetIteratorNext");
+
+ // Steps 4-5 (implemented in _GetNextSetEntryForIterator).
+ // Steps 8-9 (omitted).
+
+ var setIterationResult = setIteratorTemp.setIterationResult;
+ if (!setIterationResult) {
+ setIterationResult = setIteratorTemp.setIterationResult = _CreateSetIterationResult();
+ }
+
+ var retVal = {value: undefined, done: true};
+
+ // Steps 10.a, 11.
+ var done = _GetNextSetEntryForIterator(O, setIterationResult);
+ if (!done) {
+ // Steps 10.b-c (omitted).
+
+ // Step 6.
+ var itemKind = UnsafeGetInt32FromReservedSlot(this, ITERATOR_SLOT_ITEM_KIND);
+
+ var result;
+ if (itemKind === ITEM_KIND_VALUE) {
+ // Step 10.d.i.
+ result = setIterationResult[0];
+ } else {
+ // Step 10.d.ii.
+ assert(itemKind === ITEM_KIND_KEY_AND_VALUE, itemKind);
+ result = [setIterationResult[0], setIterationResult[0]];
+ }
+
+ setIterationResult[0] = null;
+ retVal.value = result;
+ retVal.done = false;
+ }
+
+ // Steps 7, 10.d, 12.
+ return retVal;
+}
diff --git a/js/src/builtin/Sorting.js b/js/src/builtin/Sorting.js
new file mode 100644
index 000000000..660ab079f
--- /dev/null
+++ b/js/src/builtin/Sorting.js
@@ -0,0 +1,372 @@
+/* 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/. */
+
+// We use varying sorts across the self-hosted codebase. All sorts are
+// consolidated here to avoid confusion and re-implementation of existing
+// algorithms.
+
+// For sorting values with limited range; uint8 and int8.
+function CountingSort(array, len, signed) {
+ var buffer = new List();
+ var min = 0;
+
+ // Map int8 values onto the uint8 range when storing in buffer.
+ if (signed) {
+ min = -128;
+ }
+
+ for (var i = 0; i < 256; i++) {
+ buffer[i] = 0;
+ }
+
+ // Populate the buffer
+ for (var i = 0; i < len; i++) {
+ var val = array[i];
+ buffer[val - min]++
+ }
+
+ // Traverse the buffer in order and write back elements to array
+ var val = 0;
+ for (var i = 0; i < len; i++) {
+ // Invariant: sum(buffer[val:]) == len-i
+ while (true) {
+ if (buffer[val] > 0) {
+ array[i] = val + min;
+ buffer[val]--;
+ break;
+ } else {
+ val++;
+ }
+ }
+ }
+ return array;
+}
+
+// Helper for RadixSort
+function ByteAtCol(x, pos) {
+ return (x >> (pos * 8)) & 0xFF;
+}
+
+function SortByColumn(array, len, aux, col) {
+ const R = 256;
+ let counts = new List();
+
+ // |counts| is used to compute the starting index position for each key.
+ // Letting counts[0] always be 0, simplifies the transform step below.
+ // Example:
+ //
+ // Computing frequency counts for the input [1 2 1] gives:
+ // 0 1 2 3 ... (keys)
+ // 0 0 2 1 (frequencies)
+ //
+ // Transforming frequencies to indexes gives:
+ // 0 1 2 3 ... (keys)
+ // 0 0 2 3 (indexes)
+ for (let r = 0; r < R + 1; r++) {
+ counts[r] = 0;
+ }
+ // Compute frequency counts
+ for (let i = 0; i < len; i++) {
+ let val = array[i];
+ let b = ByteAtCol(val, col);
+ counts[b + 1]++;
+ }
+
+ // Transform counts to indices.
+ for (let r = 0; r < R; r++) {
+ counts[r+1] += counts[r];
+ }
+
+ // Distribute
+ for (let i = 0; i < len; i++) {
+ let val = array[i];
+ let b = ByteAtCol(val, col);
+ aux[counts[b]++] = val;
+ }
+
+ // Copy back
+ for (let i = 0; i < len; i++) {
+ array[i] = aux[i];
+ }
+}
+
+// Sorts integers and float32. |signed| is true for int16 and int32, |floating|
+// is true for float32.
+function RadixSort(array, len, buffer, nbytes, signed, floating, comparefn) {
+
+ // Determined by performance testing.
+ if (len < 128) {
+ QuickSort(array, len, comparefn);
+ return array;
+ }
+
+ let aux = new List();
+ for (let i = 0; i < len; i++) {
+ aux[i] = 0;
+ }
+
+ let view = array;
+ let signMask = 1 << nbytes * 8 - 1;
+
+ // Preprocess
+ if (floating) {
+ // This happens if the array object is constructed under JIT
+ if (buffer === null) {
+ buffer = callFunction(std_TypedArray_buffer, array);
+ }
+
+ // Verify that the buffer is non-null
+ assert(buffer !== null, "Attached data buffer should be reified when array length is >= 128.");
+
+ view = new Int32Array(buffer);
+
+ // Flip sign bit for positive numbers; flip all bits for negative
+ // numbers
+ for (let i = 0; i < len; i++) {
+ if (view[i] & signMask) {
+ view[i] ^= 0xFFFFFFFF;
+ } else {
+ view[i] ^= signMask
+ }
+ }
+ } else if (signed) {
+ // Flip sign bit
+ for (let i = 0; i < len; i++) {
+ view[i] ^= signMask
+ }
+ }
+
+ // Sort
+ for (let col = 0; col < nbytes; col++) {
+ SortByColumn(view, len, aux, col);
+ }
+
+ // Restore original bit representation
+ if (floating) {
+ for (let i = 0; i < len; i++) {
+ if (view[i] & signMask) {
+ view[i] ^= signMask;
+ } else {
+ view[i] ^= 0xFFFFFFFF;
+ }
+ }
+ } else if (signed) {
+ for (let i = 0; i < len; i++) {
+ view[i] ^= signMask
+ }
+ }
+ return array;
+}
+
+
+// For sorting small arrays.
+function InsertionSort(array, from, to, comparefn) {
+ let item, swap, i, j;
+ for (i = from + 1; i <= to; i++) {
+ item = array[i];
+ for (j = i - 1; j >= from; j--) {
+ swap = array[j];
+ if (comparefn(swap, item) <= 0)
+ break;
+ array[j + 1] = swap;
+ }
+ array[j + 1] = item;
+ }
+}
+
+function SwapArrayElements(array, i, j) {
+ var swap = array[i];
+ array[i] = array[j];
+ array[j] = swap;
+}
+
+// A helper function for MergeSort.
+function Merge(list, start, mid, end, lBuffer, rBuffer, comparefn) {
+ var i, j, k;
+
+ var sizeLeft = mid - start + 1;
+ var sizeRight = end - mid;
+
+ // Copy our virtual lists into separate buffers.
+ for (i = 0; i < sizeLeft; i++)
+ lBuffer[i] = list[start + i];
+
+ for (j = 0; j < sizeRight; j++)
+ rBuffer[j] = list[mid + 1 + j];
+
+
+ i = 0;
+ j = 0;
+ k = start;
+ while (i < sizeLeft && j < sizeRight) {
+ if (comparefn(lBuffer[i], rBuffer[j]) <= 0) {
+ list[k] = lBuffer[i];
+ i++;
+ } else {
+ list[k] = rBuffer[j];
+ j++;
+ }
+ k++;
+ }
+
+ // Empty out any remaining elements in the buffer.
+ while (i < sizeLeft) {
+ list[k] =lBuffer[i];
+ i++;
+ k++;
+ }
+
+ while (j < sizeRight) {
+ list[k] =rBuffer[j];
+ j++;
+ k++;
+ }
+}
+
+// Helper function for overwriting a sparse array with a
+// dense array, filling remaining slots with holes.
+function MoveHoles(sparse, sparseLen, dense, denseLen) {
+ for (var i = 0; i < denseLen; i++)
+ sparse[i] = dense[i];
+ for (var j = denseLen; j < sparseLen; j++)
+ delete sparse[j];
+}
+
+// Iterative, bottom up, mergesort.
+function MergeSort(array, len, comparefn) {
+ // Until recently typed arrays had no sort method. To work around that
+ // many users passed them to Array.prototype.sort. Now that we have a
+ // typed array specific sorting method it makes sense to divert to it
+ // when possible.
+ if (IsPossiblyWrappedTypedArray(array)) {
+ return callFunction(TypedArraySort, array, comparefn);
+ }
+
+ // To save effort we will do all of our work on a dense list,
+ // then create holes at the end.
+ var denseList = new List();
+ var denseLen = 0;
+
+ for (var i = 0; i < len; i++) {
+ if (i in array)
+ denseList[denseLen++] = array[i];
+ }
+
+ if (denseLen < 1)
+ return array;
+
+ // Insertion sort for small arrays, where "small" is defined by performance
+ // testing.
+ if (denseLen < 24) {
+ InsertionSort(denseList, 0, denseLen - 1, comparefn);
+ MoveHoles(array, len, denseList, denseLen);
+ return array;
+ }
+
+ // We do all of our allocating up front
+ var lBuffer = new List();
+ var rBuffer = new List();
+
+ var mid, end, endOne, endTwo;
+ for (var windowSize = 1; windowSize < denseLen; windowSize = 2 * windowSize) {
+ for (var start = 0; start < denseLen - 1; start += 2 * windowSize) {
+ assert(windowSize < denseLen, "The window size is larger than the array denseLength!");
+ // The midpoint between the two subarrays.
+ mid = start + windowSize - 1;
+ // To keep from going over the edge.
+ end = start + 2 * windowSize - 1;
+ end = end < denseLen - 1 ? end : denseLen - 1;
+ // Skip lopsided runs to avoid doing useless work
+ if (mid > end)
+ continue;
+ Merge(denseList, start, mid, end, lBuffer, rBuffer, comparefn);
+ }
+ }
+ MoveHoles(array, len, denseList, denseLen);
+ return array;
+}
+
+// Rearranges the elements in array[from:to + 1] and returns an index j such that:
+// - from < j < to
+// - each element in array[from:j] is less than or equal to array[j]
+// - each element in array[j + 1:to + 1] greater than or equal to array[j].
+function Partition(array, from, to, comparefn) {
+ assert(to - from >= 3, "Partition will not work with less than three elements");
+
+ var medianIndex = (from + to) >> 1;
+
+ var i = from + 1;
+ var j = to;
+
+ SwapArrayElements(array, medianIndex, i);
+
+ // Median of three pivot selection.
+ if (comparefn(array[from], array[to]) > 0)
+ SwapArrayElements(array, from, to);
+
+ if (comparefn(array[i], array[to]) > 0)
+ SwapArrayElements(array, i, to);
+
+ if (comparefn(array[from], array[i]) > 0)
+ SwapArrayElements(array, from, i);
+
+ var pivotIndex = i;
+
+ // Hoare partition method.
+ for(;;) {
+ do i++; while (comparefn(array[i], array[pivotIndex]) < 0);
+ do j--; while (comparefn(array[j], array[pivotIndex]) > 0);
+ if (i > j)
+ break;
+ SwapArrayElements(array, i, j);
+ }
+
+ SwapArrayElements(array, pivotIndex, j);
+ return j;
+}
+
+// In-place QuickSort.
+function QuickSort(array, len, comparefn) {
+ // Managing the stack ourselves seems to provide a small performance boost.
+ var stack = new List();
+ var top = 0;
+
+ var start = 0;
+ var end = len - 1;
+
+ var pivotIndex, i, j, leftLen, rightLen;
+
+ for (;;) {
+ // Insertion sort for the first N elements where N is some value
+ // determined by performance testing.
+ if (end - start <= 23) {
+ InsertionSort(array, start, end, comparefn);
+ if (top < 1)
+ break;
+ end = stack[--top];
+ start = stack[--top];
+ } else {
+ pivotIndex = Partition(array, start, end, comparefn);
+
+ // Calculate the left and right sub-array lengths and save
+ // stack space by directly modifying start/end so that
+ // we sort the longest of the two during the next iteration.
+ // This reduces the maximum stack size to log2(len).
+ leftLen = (pivotIndex - 1) - start;
+ rightLen = end - (pivotIndex + 1);
+
+ if (rightLen > leftLen) {
+ stack[top++] = start;
+ stack[top++] = pivotIndex - 1;
+ start = pivotIndex + 1;
+ } else {
+ stack[top++] = pivotIndex + 1;
+ stack[top++] = end;
+ end = pivotIndex - 1;
+ }
+
+ }
+ }
+ return array;
+}
diff --git a/js/src/builtin/String.js b/js/src/builtin/String.js
new file mode 100644
index 000000000..4202d0de6
--- /dev/null
+++ b/js/src/builtin/String.js
@@ -0,0 +1,872 @@
+/* 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/. */
+
+/*global intl_Collator: false, */
+
+function StringProtoHasNoMatch() {
+ var ObjectProto = GetBuiltinPrototype("Object");
+ var StringProto = GetBuiltinPrototype("String");
+ if (!ObjectHasPrototype(StringProto, ObjectProto))
+ return false;
+ return !(std_match in StringProto);
+}
+
+function IsStringMatchOptimizable() {
+ var RegExpProto = GetBuiltinPrototype("RegExp");
+ // If RegExpPrototypeOptimizable succeeds, `exec` and `@@match` are
+ // guaranteed to be data properties.
+ return RegExpPrototypeOptimizable(RegExpProto) &&
+ RegExpProto.exec === RegExp_prototype_Exec &&
+ RegExpProto[std_match] === RegExpMatch;
+}
+
+// ES 2016 draft Mar 25, 2016 21.1.3.11.
+function String_match(regexp) {
+ // Step 1.
+ RequireObjectCoercible(this);
+
+ // Step 2.
+ var isPatternString = (typeof regexp === "string");
+ if (!(isPatternString && StringProtoHasNoMatch()) && regexp !== undefined && regexp !== null) {
+ // Step 2.a.
+ var matcher = GetMethod(regexp, std_match);
+
+ // Step 2.b.
+ if (matcher !== undefined)
+ return callContentFunction(matcher, regexp, this);
+ }
+
+ // Step 3.
+ var S = ToString(this);
+
+ if (isPatternString && IsStringMatchOptimizable()) {
+ var flatResult = FlatStringMatch(S, regexp);
+ if (flatResult !== undefined)
+ return flatResult;
+ }
+
+ // Step 4.
+ var rx = RegExpCreate(regexp);
+
+ // Step 5 (optimized case).
+ if (IsStringMatchOptimizable())
+ return RegExpMatcher(rx, S, 0);
+
+ // Step 5.
+ return callContentFunction(GetMethod(rx, std_match), rx, S);
+}
+
+function String_generic_match(thisValue, regexp) {
+ if (thisValue === undefined)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.match');
+ return callFunction(String_match, thisValue, regexp);
+}
+
+/**
+ * A helper function implementing the logic for both String.prototype.padStart
+ * and String.prototype.padEnd as described in ES7 Draft March 29, 2016
+ */
+function String_pad(maxLength, fillString, padEnd=false) {
+
+ // Steps 1-2.
+ RequireObjectCoercible(this);
+ let str = ToString(this);
+
+ // Steps 3-4.
+ let intMaxLength = ToLength(maxLength);
+ let strLen = str.length;
+
+ // Step 5.
+ if (intMaxLength <= strLen)
+ return str;
+
+ // Steps 6-7.
+ let filler = fillString === undefined ? " " : ToString(fillString);
+
+ // Step 8.
+ if (filler === "")
+ return str;
+
+ // Step 9.
+ let fillLen = intMaxLength - strLen;
+
+ // Step 10.
+ let truncatedStringFiller = callFunction(String_repeat, filler,
+ fillLen / filler.length);
+
+ truncatedStringFiller += callFunction(String_substr, filler, 0,
+ fillLen % filler.length);
+
+ // Step 11.
+ if (padEnd === true)
+ return str + truncatedStringFiller;
+ return truncatedStringFiller + str;
+}
+
+function String_pad_start(maxLength, fillString=" ") {
+ return callFunction(String_pad, this, maxLength, fillString, false);
+}
+
+function String_pad_end(maxLength, fillString=" ") {
+ return callFunction(String_pad, this, maxLength, fillString, true);
+}
+
+function StringProtoHasNoReplace() {
+ var ObjectProto = GetBuiltinPrototype("Object");
+ var StringProto = GetBuiltinPrototype("String");
+ if (!ObjectHasPrototype(StringProto, ObjectProto))
+ return false;
+ return !(std_replace in StringProto);
+}
+
+// A thin wrapper to call SubstringKernel with int32-typed arguments.
+// Caller should check the range of |from| and |length|.
+function Substring(str, from, length) {
+ assert(typeof str === "string", "|str| should be a string");
+ assert(from | 0 === from, "coercing |from| into int32 should not change the value");
+ assert(length | 0 === length, "coercing |length| into int32 should not change the value");
+
+ return SubstringKernel(str, from | 0, length | 0);
+}
+
+// ES 2016 draft Mar 25, 2016 21.1.3.14.
+function String_replace(searchValue, replaceValue) {
+ // Step 1.
+ RequireObjectCoercible(this);
+
+ // Step 2.
+ if (!(typeof searchValue === "string" && StringProtoHasNoReplace()) &&
+ searchValue !== undefined && searchValue !== null)
+ {
+ // Step 2.a.
+ var replacer = GetMethod(searchValue, std_replace);
+
+ // Step 2.b.
+ if (replacer !== undefined)
+ return callContentFunction(replacer, searchValue, this, replaceValue);
+ }
+
+ // Step 3.
+ var string = ToString(this);
+
+ // Step 4.
+ var searchString = ToString(searchValue);
+
+ if (typeof replaceValue === "string") {
+ // Steps 6-12: Optimized for string case.
+ return StringReplaceString(string, searchString, replaceValue);
+ }
+
+ // Step 5.
+ if (!IsCallable(replaceValue)) {
+ // Steps 6-12.
+ return StringReplaceString(string, searchString, ToString(replaceValue));
+ }
+
+ // Step 7.
+ var pos = callFunction(std_String_indexOf, string, searchString);
+ if (pos === -1)
+ return string;
+
+ // Step 8.
+ var replStr = ToString(callContentFunction(replaceValue, undefined, searchString, pos, string));
+
+ // Step 10.
+ var tailPos = pos + searchString.length;
+
+ // Step 11.
+ var newString;
+ if (pos === 0)
+ newString = "";
+ else
+ newString = Substring(string, 0, pos);
+
+ newString += replStr;
+ var stringLength = string.length;
+ if (tailPos < stringLength)
+ newString += Substring(string, tailPos, stringLength - tailPos);
+
+ // Step 12.
+ return newString;
+}
+
+function String_generic_replace(thisValue, searchValue, replaceValue) {
+ if (thisValue === undefined)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.replace');
+ return callFunction(String_replace, thisValue, searchValue, replaceValue);
+}
+
+function StringProtoHasNoSearch() {
+ var ObjectProto = GetBuiltinPrototype("Object");
+ var StringProto = GetBuiltinPrototype("String");
+ if (!ObjectHasPrototype(StringProto, ObjectProto))
+ return false;
+ return !(std_search in StringProto);
+}
+
+function IsStringSearchOptimizable() {
+ var RegExpProto = GetBuiltinPrototype("RegExp");
+ // If RegExpPrototypeOptimizable succeeds, `exec` and `@@search` are
+ // guaranteed to be data properties.
+ return RegExpPrototypeOptimizable(RegExpProto) &&
+ RegExpProto.exec === RegExp_prototype_Exec &&
+ RegExpProto[std_search] === RegExpSearch;
+}
+
+// ES 2016 draft Mar 25, 2016 21.1.3.15.
+function String_search(regexp) {
+ // Step 1.
+ RequireObjectCoercible(this);
+
+ // Step 2.
+ var isPatternString = (typeof regexp === "string");
+ if (!(isPatternString && StringProtoHasNoSearch()) && regexp !== undefined && regexp !== null) {
+ // Step 2.a.
+ var searcher = GetMethod(regexp, std_search);
+
+ // Step 2.b.
+ if (searcher !== undefined)
+ return callContentFunction(searcher, regexp, this);
+ }
+
+ // Step 3.
+ var string = ToString(this);
+
+ if (isPatternString && IsStringSearchOptimizable()) {
+ var flatResult = FlatStringSearch(string, regexp);
+ if (flatResult !== -2)
+ return flatResult;
+ }
+
+ // Step 4.
+ var rx = RegExpCreate(regexp);
+
+ // Step 5.
+ return callContentFunction(GetMethod(rx, std_search), rx, string);
+}
+
+function String_generic_search(thisValue, regexp) {
+ if (thisValue === undefined)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.search');
+ return callFunction(String_search, thisValue, regexp);
+}
+
+function StringProtoHasNoSplit() {
+ var ObjectProto = GetBuiltinPrototype("Object");
+ var StringProto = GetBuiltinPrototype("String");
+ if (!ObjectHasPrototype(StringProto, ObjectProto))
+ return false;
+ return !(std_split in StringProto);
+}
+
+// ES 2016 draft Mar 25, 2016 21.1.3.17.
+function String_split(separator, limit) {
+ // Step 1.
+ RequireObjectCoercible(this);
+
+ // Optimized path for string.split(string), especially when both strings
+ // are constants. Following sequence of if's cannot be put together in
+ // order that IonMonkey sees the constant if present (bug 1246141).
+ if (typeof this === "string") {
+ if (StringProtoHasNoSplit()) {
+ if (typeof separator === "string") {
+ if (limit === undefined) {
+ // inlineConstantStringSplitString needs both arguments to
+ // be MConstant, so pass them directly.
+ return StringSplitString(this, separator);
+ }
+ }
+ }
+ }
+
+ // Step 2.
+ if (!(typeof separator == "string" && StringProtoHasNoSplit()) &&
+ separator !== undefined && separator !== null)
+ {
+ // Step 2.a.
+ var splitter = GetMethod(separator, std_split);
+
+ // Step 2.b.
+ if (splitter !== undefined)
+ return callContentFunction(splitter, separator, this, limit);
+ }
+
+ // Step 3.
+ var S = ToString(this);
+
+ // Step 6.
+ var R;
+ if (limit !== undefined) {
+ var lim = limit >>> 0;
+
+ // Step 9.
+ R = ToString(separator);
+
+ // Step 10.
+ if (lim === 0)
+ return [];
+
+ // Step 11.
+ if (separator === undefined)
+ return [S];
+
+ // Steps 4, 8, 12-18.
+ return StringSplitStringLimit(S, R, lim);
+ }
+
+ // Step 9.
+ R = ToString(separator);
+
+ // Step 11.
+ if (separator === undefined)
+ return [S];
+
+ // Optimized path.
+ // Steps 4, 8, 12-18.
+ return StringSplitString(S, R);
+}
+
+function String_generic_split(thisValue, separator, limit) {
+ if (thisValue === undefined)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.split');
+ return callFunction(String_split, thisValue, separator, limit);
+}
+
+/* ES6 Draft Oct 14, 2014 21.1.3.19 */
+function String_substring(start, end) {
+ // Steps 1-3.
+ RequireObjectCoercible(this);
+ var str = ToString(this);
+
+ // Step 4.
+ var len = str.length;
+
+ // Step 5.
+ var intStart = ToInteger(start);
+
+ // Step 6.
+ var intEnd = (end === undefined) ? len : ToInteger(end);
+
+ // Step 7.
+ var finalStart = std_Math_min(std_Math_max(intStart, 0), len);
+
+ // Step 8.
+ var finalEnd = std_Math_min(std_Math_max(intEnd, 0), len);
+
+ // Steps 9-10.
+ var from, to;
+ if (finalStart < finalEnd) {
+ from = finalStart;
+ to = finalEnd;
+ } else {
+ from = finalEnd;
+ to = finalStart;
+ }
+
+ // Step 11.
+ // While |from| and |to - from| are bounded to the length of |str| and this
+ // and thus definitely in the int32 range, they can still be typed as
+ // double. Eagerly truncate since SubstringKernel only accepts int32.
+ return SubstringKernel(str, from | 0, (to - from) | 0);
+}
+
+function String_static_substring(string, start, end) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.substring');
+ return callFunction(String_substring, string, start, end);
+}
+
+/* ES6 Draft Oct 14, 2014 B.2.3.1 */
+function String_substr(start, length) {
+ // Steps 1-2.
+ RequireObjectCoercible(this);
+ var str = ToString(this);
+
+ // Steps 3-4.
+ var intStart = ToInteger(start);
+
+ // Steps 5-7.
+ var size = str.length;
+ // Use |size| instead of +Infinity to avoid performing calculations with
+ // doubles. (The result is the same either way.)
+ var end = (length === undefined) ? size : ToInteger(length);
+
+ // Step 8.
+ if (intStart < 0)
+ intStart = std_Math_max(intStart + size, 0);
+
+ // Step 9.
+ var resultLength = std_Math_min(std_Math_max(end, 0), size - intStart);
+
+ // Step 10.
+ if (resultLength <= 0)
+ return "";
+
+ // Step 11.
+ // While |intStart| and |resultLength| are bounded to the length of |str|
+ // and thus definitely in the int32 range, they can still be typed as
+ // double. Eagerly truncate since SubstringKernel only accepts int32.
+ return SubstringKernel(str, intStart | 0, resultLength | 0);
+}
+
+function String_static_substr(string, start, length) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.substr');
+ return callFunction(String_substr, string, start, length);
+}
+
+/* ES6 Draft Oct 14, 2014 21.1.3.16 */
+function String_slice(start, end) {
+ // Steps 1-3.
+ RequireObjectCoercible(this);
+ var str = ToString(this);
+
+ // Step 4.
+ var len = str.length;
+
+ // Step 5.
+ var intStart = ToInteger(start);
+
+ // Step 6.
+ var intEnd = (end === undefined) ? len : ToInteger(end);
+
+ // Step 7.
+ var from = (intStart < 0) ? std_Math_max(len + intStart, 0) : std_Math_min(intStart, len);
+
+ // Step 8.
+ var to = (intEnd < 0) ? std_Math_max(len + intEnd, 0) : std_Math_min(intEnd, len);
+
+ // Step 9.
+ var span = std_Math_max(to - from, 0);
+
+ // Step 10.
+ // While |from| and |span| are bounded to the length of |str|
+ // and thus definitely in the int32 range, they can still be typed as
+ // double. Eagerly truncate since SubstringKernel only accepts int32.
+ return SubstringKernel(str, from | 0, span | 0);
+}
+
+function String_static_slice(string, start, end) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.slice');
+ return callFunction(String_slice, string, start, end);
+}
+
+/* ES6 Draft September 5, 2013 21.1.3.3 */
+function String_codePointAt(pos) {
+ // Steps 1-3.
+ RequireObjectCoercible(this);
+ var S = ToString(this);
+
+ // Steps 4-5.
+ var position = ToInteger(pos);
+
+ // Step 6.
+ var size = S.length;
+
+ // Step 7.
+ if (position < 0 || position >= size)
+ return undefined;
+
+ // Steps 8-9.
+ var first = callFunction(std_String_charCodeAt, S, position);
+ if (first < 0xD800 || first > 0xDBFF || position + 1 === size)
+ return first;
+
+ // Steps 10-11.
+ var second = callFunction(std_String_charCodeAt, S, position + 1);
+ if (second < 0xDC00 || second > 0xDFFF)
+ return first;
+
+ // Step 12.
+ return (first - 0xD800) * 0x400 + (second - 0xDC00) + 0x10000;
+}
+
+var collatorCache = new Record();
+
+/* ES6 20121122 draft 15.5.4.21. */
+function String_repeat(count) {
+ // Steps 1-3.
+ RequireObjectCoercible(this);
+ var S = ToString(this);
+
+ // Steps 4-5.
+ var n = ToInteger(count);
+
+ // Steps 6-7.
+ if (n < 0)
+ ThrowRangeError(JSMSG_NEGATIVE_REPETITION_COUNT);
+
+ if (!(n * S.length < (1 << 28)))
+ ThrowRangeError(JSMSG_RESULTING_STRING_TOO_LARGE);
+
+ // Communicate |n|'s possible range to the compiler.
+ n = n & ((1 << 28) - 1);
+
+ // Steps 8-9.
+ var T = "";
+ for (;;) {
+ if (n & 1)
+ T += S;
+ n >>= 1;
+ if (n)
+ S += S;
+ else
+ break;
+ }
+ return T;
+}
+
+// ES6 draft specification, section 21.1.3.27, version 2013-09-27.
+function String_iterator() {
+ RequireObjectCoercible(this);
+ var S = ToString(this);
+ var iterator = NewStringIterator();
+ UnsafeSetReservedSlot(iterator, ITERATOR_SLOT_TARGET, S);
+ UnsafeSetReservedSlot(iterator, ITERATOR_SLOT_NEXT_INDEX, 0);
+ return iterator;
+}
+
+function StringIteratorNext() {
+ if (!IsObject(this) || !IsStringIterator(this)) {
+ return callFunction(CallStringIteratorMethodIfWrapped, this,
+ "StringIteratorNext");
+ }
+
+ var S = UnsafeGetStringFromReservedSlot(this, ITERATOR_SLOT_TARGET);
+ // We know that JSString::MAX_LENGTH <= INT32_MAX (and assert this in
+ // SelfHostring.cpp) so our current index can never be anything other than
+ // an Int32Value.
+ var index = UnsafeGetInt32FromReservedSlot(this, ITERATOR_SLOT_NEXT_INDEX);
+ var size = S.length;
+ var result = { value: undefined, done: false };
+
+ if (index >= size) {
+ result.done = true;
+ return result;
+ }
+
+ var charCount = 1;
+ var first = callFunction(std_String_charCodeAt, S, index);
+ if (first >= 0xD800 && first <= 0xDBFF && index + 1 < size) {
+ var second = callFunction(std_String_charCodeAt, S, index + 1);
+ if (second >= 0xDC00 && second <= 0xDFFF) {
+ charCount = 2;
+ }
+ }
+
+ UnsafeSetReservedSlot(this, ITERATOR_SLOT_NEXT_INDEX, index + charCount);
+ result.value = callFunction(String_substring, S, index, index + charCount);
+
+ return result;
+}
+
+/**
+ * Compare this String against that String, using the locale and collation
+ * options provided.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 13.1.1.
+ */
+function String_localeCompare(that) {
+ // Steps 1-3.
+ RequireObjectCoercible(this);
+ var S = ToString(this);
+ var That = ToString(that);
+
+ // Steps 4-5.
+ var locales = arguments.length > 1 ? arguments[1] : undefined;
+ var options = arguments.length > 2 ? arguments[2] : undefined;
+
+ // Step 6.
+ var collator;
+ if (locales === undefined && options === undefined) {
+ // This cache only optimizes for the old ES5 localeCompare without
+ // locales and options.
+ if (collatorCache.collator === undefined)
+ collatorCache.collator = intl_Collator(locales, options);
+ collator = collatorCache.collator;
+ } else {
+ collator = intl_Collator(locales, options);
+ }
+
+ // Step 7.
+ return intl_CompareStrings(collator, S, That);
+}
+
+/* ES6 Draft May 22, 2014 21.1.2.4 */
+function String_static_raw(callSite, ...substitutions) {
+ // Step 1 (implicit).
+ // Step 2.
+ var numberOfSubstitutions = substitutions.length;
+
+ // Steps 3-4.
+ var cooked = ToObject(callSite);
+
+ // Steps 5-7.
+ var raw = ToObject(cooked.raw);
+
+ // Steps 8-10.
+ var literalSegments = ToLength(raw.length);
+
+ // Step 11.
+ if (literalSegments <= 0)
+ return "";
+
+ // Step 12.
+ var resultString = "";
+
+ // Step 13.
+ var nextIndex = 0;
+
+ // Step 14.
+ while (true) {
+ // Steps a-d.
+ var nextSeg = ToString(raw[nextIndex]);
+
+ // Step e.
+ resultString = resultString + nextSeg;
+
+ // Step f.
+ if (nextIndex + 1 === literalSegments)
+ // Step f.i.
+ return resultString;
+
+ // Steps g-j.
+ var nextSub;
+ if (nextIndex < numberOfSubstitutions)
+ nextSub = ToString(substitutions[nextIndex]);
+ else
+ nextSub = "";
+
+ // Step k.
+ resultString = resultString + nextSub;
+
+ // Step l.
+ nextIndex++;
+ }
+}
+
+/**
+ * Compare String str1 against String str2, using the locale and collation
+ * options provided.
+ *
+ * Mozilla proprietary.
+ * Spec: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String#String_generic_methods
+ */
+function String_static_localeCompare(str1, str2) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "String.localeCompare");
+ var locales = arguments.length > 2 ? arguments[2] : undefined;
+ var options = arguments.length > 3 ? arguments[3] : undefined;
+#if EXPOSE_INTL_API
+ return callFunction(String_localeCompare, str1, str2, locales, options);
+#else
+ return callFunction(std_String_localeCompare, str1, str2, locales, options);
+#endif
+}
+
+// ES6 draft 2014-04-27 B.2.3.3
+function String_big() {
+ RequireObjectCoercible(this);
+ return "<big>" + ToString(this) + "</big>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.4
+function String_blink() {
+ RequireObjectCoercible(this);
+ return "<blink>" + ToString(this) + "</blink>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.5
+function String_bold() {
+ RequireObjectCoercible(this);
+ return "<b>" + ToString(this) + "</b>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.6
+function String_fixed() {
+ RequireObjectCoercible(this);
+ return "<tt>" + ToString(this) + "</tt>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.9
+function String_italics() {
+ RequireObjectCoercible(this);
+ return "<i>" + ToString(this) + "</i>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.11
+function String_small() {
+ RequireObjectCoercible(this);
+ return "<small>" + ToString(this) + "</small>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.12
+function String_strike() {
+ RequireObjectCoercible(this);
+ return "<strike>" + ToString(this) + "</strike>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.13
+function String_sub() {
+ RequireObjectCoercible(this);
+ return "<sub>" + ToString(this) + "</sub>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.14
+function String_sup() {
+ RequireObjectCoercible(this);
+ return "<sup>" + ToString(this) + "</sup>";
+}
+
+function EscapeAttributeValue(v) {
+ var inputStr = ToString(v);
+ var inputLen = inputStr.length;
+ var outputStr = "";
+ var chunkStart = 0;
+ for (var i = 0; i < inputLen; i++) {
+ if (inputStr[i] === '"') {
+ outputStr += callFunction(String_substring, inputStr, chunkStart, i) + '&quot;';
+ chunkStart = i + 1;
+ }
+ }
+ if (chunkStart === 0)
+ return inputStr;
+ if (chunkStart < inputLen)
+ outputStr += callFunction(String_substring, inputStr, chunkStart);
+ return outputStr;
+}
+
+// ES6 draft 2014-04-27 B.2.3.2
+function String_anchor(name) {
+ RequireObjectCoercible(this);
+ var S = ToString(this);
+ return '<a name="' + EscapeAttributeValue(name) + '">' + S + "</a>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.7
+function String_fontcolor(color) {
+ RequireObjectCoercible(this);
+ var S = ToString(this);
+ return '<font color="' + EscapeAttributeValue(color) + '">' + S + "</font>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.8
+function String_fontsize(size) {
+ RequireObjectCoercible(this);
+ var S = ToString(this);
+ return '<font size="' + EscapeAttributeValue(size) + '">' + S + "</font>";
+}
+
+// ES6 draft 2014-04-27 B.2.3.10
+function String_link(url) {
+ RequireObjectCoercible(this);
+ var S = ToString(this);
+ return '<a href="' + EscapeAttributeValue(url) + '">' + S + "</a>";
+}
+
+function String_static_toLowerCase(string) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.toLowerCase');
+ return callFunction(std_String_toLowerCase, string);
+}
+
+function String_static_toUpperCase(string) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.toUpperCase');
+ return callFunction(std_String_toUpperCase, string);
+}
+
+function String_static_charAt(string, pos) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.charAt');
+ return callFunction(std_String_charAt, string, pos);
+}
+
+function String_static_charCodeAt(string, pos) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.charCodeAt');
+ return callFunction(std_String_charCodeAt, string, pos);
+}
+
+function String_static_includes(string, searchString) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.includes');
+ var position = arguments.length > 2 ? arguments[2] : undefined;
+ return callFunction(std_String_includes, string, searchString, position);
+}
+
+function String_static_indexOf(string, searchString) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.indexOf');
+ var position = arguments.length > 2 ? arguments[2] : undefined;
+ return callFunction(std_String_indexOf, string, searchString, position);
+}
+
+function String_static_lastIndexOf(string, searchString) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.lastIndexOf');
+ var position = arguments.length > 2 ? arguments[2] : undefined;
+ return callFunction(std_String_lastIndexOf, string, searchString, position);
+}
+
+function String_static_startsWith(string, searchString) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.startsWith');
+ var position = arguments.length > 2 ? arguments[2] : undefined;
+ return callFunction(std_String_startsWith, string, searchString, position);
+}
+
+function String_static_endsWith(string, searchString) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.endsWith');
+ var endPosition = arguments.length > 2 ? arguments[2] : undefined;
+ return callFunction(std_String_endsWith, string, searchString, endPosition);
+}
+
+function String_static_trim(string) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.trim');
+ return callFunction(std_String_trim, string);
+}
+
+function String_static_trimLeft(string) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.trimLeft');
+ return callFunction(std_String_trimLeft, string);
+}
+
+function String_static_trimRight(string) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.trimRight');
+ return callFunction(std_String_trimRight, string);
+}
+
+function String_static_toLocaleLowerCase(string) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.toLocaleLowerCase');
+ return callFunction(std_String_toLocaleLowerCase, string);
+}
+
+function String_static_toLocaleUpperCase(string) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.toLocaleUpperCase');
+ return callFunction(std_String_toLocaleUpperCase, string);
+}
+
+#if EXPOSE_INTL_API
+function String_static_normalize(string) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.normalize');
+ var form = arguments.length > 1 ? arguments[1] : undefined;
+ return callFunction(std_String_normalize, string, form);
+}
+#endif
+
+function String_static_concat(string, arg1) {
+ if (arguments.length < 1)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.concat');
+ var args = callFunction(std_Array_slice, arguments, 1);
+ return callFunction(std_Function_apply, std_String_concat, string, args);
+}
diff --git a/js/src/builtin/SymbolObject.cpp b/js/src/builtin/SymbolObject.cpp
new file mode 100644
index 000000000..cf48402d6
--- /dev/null
+++ b/js/src/builtin/SymbolObject.cpp
@@ -0,0 +1,234 @@
+/* -*- 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 "builtin/SymbolObject.h"
+
+#include "vm/StringBuffer.h"
+#include "vm/Symbol.h"
+
+#include "jsobjinlines.h"
+
+#include "vm/NativeObject-inl.h"
+
+using JS::Symbol;
+using namespace js;
+
+const Class SymbolObject::class_ = {
+ "Symbol",
+ JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Symbol)
+};
+
+SymbolObject*
+SymbolObject::create(JSContext* cx, JS::HandleSymbol symbol)
+{
+ JSObject* obj = NewBuiltinClassInstance(cx, &class_);
+ if (!obj)
+ return nullptr;
+ SymbolObject& symobj = obj->as<SymbolObject>();
+ symobj.setPrimitiveValue(symbol);
+ return &symobj;
+}
+
+const JSPropertySpec SymbolObject::properties[] = {
+ JS_PS_END
+};
+
+const JSFunctionSpec SymbolObject::methods[] = {
+ JS_FN(js_toString_str, toString, 0, 0),
+ JS_FN(js_valueOf_str, valueOf, 0, 0),
+ JS_SYM_FN(toPrimitive, toPrimitive, 1, JSPROP_READONLY),
+ JS_FS_END
+};
+
+const JSFunctionSpec SymbolObject::staticMethods[] = {
+ JS_FN("for", for_, 1, 0),
+ JS_FN("keyFor", keyFor, 1, 0),
+ JS_FS_END
+};
+
+JSObject*
+SymbolObject::initClass(JSContext* cx, HandleObject obj)
+{
+ Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+
+ // This uses &JSObject::class_ because: "The Symbol prototype object is an
+ // ordinary object. It is not a Symbol instance and does not have a
+ // [[SymbolData]] internal slot." (ES6 rev 24, 19.4.3)
+ RootedObject proto(cx, global->createBlankPrototype<PlainObject>(cx));
+ if (!proto)
+ return nullptr;
+
+ RootedFunction ctor(cx, global->createConstructor(cx, construct,
+ ClassName(JSProto_Symbol, cx), 0));
+ if (!ctor)
+ return nullptr;
+
+ // Define the well-known symbol properties, such as Symbol.iterator.
+ ImmutablePropertyNamePtr* names = cx->names().wellKnownSymbolNames();
+ RootedValue value(cx);
+ unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT;
+ WellKnownSymbols* wks = cx->runtime()->wellKnownSymbols;
+ for (size_t i = 0; i < JS::WellKnownSymbolLimit; i++) {
+ value.setSymbol(wks->get(i));
+ if (!NativeDefineProperty(cx, ctor, names[i], value, nullptr, nullptr, attrs))
+ return nullptr;
+ }
+
+ if (!LinkConstructorAndPrototype(cx, ctor, proto) ||
+ !DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
+ !DefineToStringTag(cx, proto, cx->names().Symbol) ||
+ !DefinePropertiesAndFunctions(cx, ctor, nullptr, staticMethods) ||
+ !GlobalObject::initBuiltinConstructor(cx, global, JSProto_Symbol, ctor, proto))
+ {
+ return nullptr;
+ }
+ return proto;
+}
+
+// ES6 rev 24 (2014 Apr 27) 19.4.1.1 and 19.4.1.2
+bool
+SymbolObject::construct(JSContext* cx, unsigned argc, Value* vp)
+{
+ // According to a note in the draft standard, "Symbol has ordinary
+ // [[Construct]] behaviour but the definition of its @@create method causes
+ // `new Symbol` to throw a TypeError exception." We do not support @@create
+ // yet, so just throw a TypeError.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.isConstructing()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_CONSTRUCTOR, "Symbol");
+ return false;
+ }
+
+ // steps 1-3
+ RootedString desc(cx);
+ if (!args.get(0).isUndefined()) {
+ desc = ToString(cx, args.get(0));
+ if (!desc)
+ return false;
+ }
+
+ // step 4
+ RootedSymbol symbol(cx, JS::Symbol::new_(cx, JS::SymbolCode::UniqueSymbol, desc));
+ if (!symbol)
+ return false;
+ args.rval().setSymbol(symbol);
+ return true;
+}
+
+// ES6 rev 24 (2014 Apr 27) 19.4.2.2
+bool
+SymbolObject::for_(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // steps 1-2
+ RootedString stringKey(cx, ToString(cx, args.get(0)));
+ if (!stringKey)
+ return false;
+
+ // steps 3-7
+ JS::Symbol* symbol = JS::Symbol::for_(cx, stringKey);
+ if (!symbol)
+ return false;
+ args.rval().setSymbol(symbol);
+ return true;
+}
+
+// ES6 rev 25 (2014 May 22) 19.4.2.7
+bool
+SymbolObject::keyFor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // step 1
+ HandleValue arg = args.get(0);
+ if (!arg.isSymbol()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK,
+ arg, nullptr, "not a symbol", nullptr);
+ return false;
+ }
+
+ // step 2
+ if (arg.toSymbol()->code() == JS::SymbolCode::InSymbolRegistry) {
+#ifdef DEBUG
+ RootedString desc(cx, arg.toSymbol()->description());
+ MOZ_ASSERT(Symbol::for_(cx, desc) == arg.toSymbol());
+#endif
+ args.rval().setString(arg.toSymbol()->description());
+ return true;
+ }
+
+ // step 3: omitted
+ // step 4
+ args.rval().setUndefined();
+ return true;
+}
+
+MOZ_ALWAYS_INLINE bool
+IsSymbol(HandleValue v)
+{
+ return v.isSymbol() || (v.isObject() && v.toObject().is<SymbolObject>());
+}
+
+// ES6 rev 27 (2014 Aug 24) 19.4.3.2
+bool
+SymbolObject::toString_impl(JSContext* cx, const CallArgs& args)
+{
+ // steps 1-3
+ HandleValue thisv = args.thisv();
+ MOZ_ASSERT(IsSymbol(thisv));
+ Rooted<Symbol*> sym(cx, thisv.isSymbol()
+ ? thisv.toSymbol()
+ : thisv.toObject().as<SymbolObject>().unbox());
+
+ // step 4
+ return SymbolDescriptiveString(cx, sym, args.rval());
+}
+
+bool
+SymbolObject::toString(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsSymbol, toString_impl>(cx, args);
+}
+
+//ES6 rev 24 (2014 Apr 27) 19.4.3.3
+bool
+SymbolObject::valueOf_impl(JSContext* cx, const CallArgs& args)
+{
+ // Step 3, the error case, is handled by CallNonGenericMethod.
+ HandleValue thisv = args.thisv();
+ MOZ_ASSERT(IsSymbol(thisv));
+ if (thisv.isSymbol())
+ args.rval().set(thisv);
+ else
+ args.rval().setSymbol(thisv.toObject().as<SymbolObject>().unbox());
+ return true;
+}
+
+bool
+SymbolObject::valueOf(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsSymbol, valueOf_impl>(cx, args);
+}
+
+// ES6 19.4.3.4
+bool
+SymbolObject::toPrimitive(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // The specification gives exactly the same algorithm for @@toPrimitive as
+ // for valueOf, so reuse the valueOf implementation.
+ return CallNonGenericMethod<IsSymbol, valueOf_impl>(cx, args);
+}
+
+JSObject*
+js::InitSymbolClass(JSContext* cx, HandleObject obj)
+{
+ return SymbolObject::initClass(cx, obj);
+}
diff --git a/js/src/builtin/SymbolObject.h b/js/src/builtin/SymbolObject.h
new file mode 100644
index 000000000..0f204b494
--- /dev/null
+++ b/js/src/builtin/SymbolObject.h
@@ -0,0 +1,65 @@
+/* -*- 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/. */
+
+#ifndef builtin_SymbolObject_h
+#define builtin_SymbolObject_h
+
+#include "vm/NativeObject.h"
+#include "vm/Symbol.h"
+
+namespace js {
+
+class SymbolObject : public NativeObject
+{
+ /* Stores this Symbol object's [[PrimitiveValue]]. */
+ static const unsigned PRIMITIVE_VALUE_SLOT = 0;
+
+ public:
+ static const unsigned RESERVED_SLOTS = 1;
+
+ static const Class class_;
+
+ static JSObject* initClass(JSContext* cx, js::HandleObject obj);
+
+ /*
+ * Creates a new Symbol object boxing the given primitive Symbol. The
+ * object's [[Prototype]] is determined from context.
+ */
+ static SymbolObject* create(JSContext* cx, JS::HandleSymbol symbol);
+
+ JS::Symbol* unbox() const {
+ return getFixedSlot(PRIMITIVE_VALUE_SLOT).toSymbol();
+ }
+
+ private:
+ inline void setPrimitiveValue(JS::Symbol* symbol) {
+ setFixedSlot(PRIMITIVE_VALUE_SLOT, SymbolValue(symbol));
+ }
+
+ static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
+
+ // Static methods.
+ static MOZ_MUST_USE bool for_(JSContext* cx, unsigned argc, Value* vp);
+ static MOZ_MUST_USE bool keyFor(JSContext* cx, unsigned argc, Value* vp);
+
+ // Methods defined on Symbol.prototype.
+ static MOZ_MUST_USE bool toString_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool toString(JSContext* cx, unsigned argc, Value* vp);
+ static MOZ_MUST_USE bool valueOf_impl(JSContext* cx, const CallArgs& args);
+ static MOZ_MUST_USE bool valueOf(JSContext* cx, unsigned argc, Value* vp);
+ static MOZ_MUST_USE bool toPrimitive(JSContext* cx, unsigned argc, Value* vp);
+
+ static const JSPropertySpec properties[];
+ static const JSFunctionSpec methods[];
+ static const JSFunctionSpec staticMethods[];
+};
+
+extern JSObject*
+InitSymbolClass(JSContext* cx, HandleObject obj);
+
+} /* namespace js */
+
+#endif /* builtin_SymbolObject_h */
diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp
new file mode 100644
index 000000000..5bc69a346
--- /dev/null
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -0,0 +1,4814 @@
+/* -*- 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 "builtin/TestingFunctions.h"
+
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Move.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Unused.h"
+
+#include <cmath>
+
+#include "jsapi.h"
+#include "jscntxt.h"
+#include "jsfriendapi.h"
+#include "jsgc.h"
+#include "jsobj.h"
+#include "jsprf.h"
+#include "jswrapper.h"
+
+#include "builtin/Promise.h"
+#include "builtin/SelfHostingDefines.h"
+#ifdef DEBUG
+#include "frontend/TokenStream.h"
+#include "irregexp/RegExpAST.h"
+#include "irregexp/RegExpEngine.h"
+#include "irregexp/RegExpParser.h"
+#endif
+#include "jit/InlinableNatives.h"
+#include "jit/JitFrameIterator.h"
+#include "js/Debug.h"
+#include "js/HashTable.h"
+#include "js/StructuredClone.h"
+#include "js/UbiNode.h"
+#include "js/UbiNodeBreadthFirst.h"
+#include "js/UbiNodeShortestPaths.h"
+#include "js/UniquePtr.h"
+#include "js/Vector.h"
+#include "vm/GlobalObject.h"
+#include "vm/Interpreter.h"
+#include "vm/ProxyObject.h"
+#include "vm/SavedStacks.h"
+#include "vm/Stack.h"
+#include "vm/StringBuffer.h"
+#include "vm/TraceLogging.h"
+#include "wasm/AsmJS.h"
+#include "wasm/WasmBinaryToExperimentalText.h"
+#include "wasm/WasmBinaryToText.h"
+#include "wasm/WasmJS.h"
+#include "wasm/WasmModule.h"
+#include "wasm/WasmSignalHandlers.h"
+#include "wasm/WasmTextToBinary.h"
+
+#include "jscntxtinlines.h"
+#include "jsobjinlines.h"
+
+#include "vm/EnvironmentObject-inl.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+using mozilla::ArrayLength;
+using mozilla::Move;
+
+// If fuzzingSafe is set, remove functionality that could cause problems with
+// fuzzers. Set this via the environment variable MOZ_FUZZING_SAFE.
+static bool fuzzingSafe = false;
+
+// If disableOOMFunctions is set, disable functionality that causes artificial
+// OOM conditions.
+static bool disableOOMFunctions = false;
+
+static bool
+EnvVarIsDefined(const char* name)
+{
+ const char* value = getenv(name);
+ return value && *value;
+}
+
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+static bool
+EnvVarAsInt(const char* name, int* valueOut)
+{
+ if (!EnvVarIsDefined(name))
+ return false;
+
+ *valueOut = atoi(getenv(name));
+ return true;
+}
+#endif
+
+static bool
+GetBuildConfiguration(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject info(cx, JS_NewPlainObject(cx));
+ if (!info)
+ return false;
+
+ if (!JS_SetProperty(cx, info, "rooting-analysis", FalseHandleValue))
+ return false;
+
+ if (!JS_SetProperty(cx, info, "exact-rooting", TrueHandleValue))
+ return false;
+
+ if (!JS_SetProperty(cx, info, "trace-jscalls-api", FalseHandleValue))
+ return false;
+
+ if (!JS_SetProperty(cx, info, "incremental-gc", TrueHandleValue))
+ return false;
+
+ if (!JS_SetProperty(cx, info, "generational-gc", TrueHandleValue))
+ return false;
+
+ RootedValue value(cx);
+#ifdef DEBUG
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "debug", value))
+ return false;
+
+#ifdef RELEASE_OR_BETA
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "release_or_beta", value))
+ return false;
+
+#ifdef JS_HAS_CTYPES
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "has-ctypes", value))
+ return false;
+
+#ifdef JS_CPU_X86
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "x86", value))
+ return false;
+
+#ifdef JS_CPU_X64
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "x64", value))
+ return false;
+
+#ifdef JS_SIMULATOR_ARM
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "arm-simulator", value))
+ return false;
+
+#ifdef JS_SIMULATOR_ARM64
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "arm64-simulator", value))
+ return false;
+
+#ifdef MOZ_ASAN
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "asan", value))
+ return false;
+
+#ifdef MOZ_TSAN
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "tsan", value))
+ return false;
+
+#ifdef JS_GC_ZEAL
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "has-gczeal", value))
+ return false;
+
+#ifdef JS_MORE_DETERMINISTIC
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "more-deterministic", value))
+ return false;
+
+#ifdef MOZ_PROFILING
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "profiling", value))
+ return false;
+
+#ifdef INCLUDE_MOZILLA_DTRACE
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "dtrace", value))
+ return false;
+
+#ifdef MOZ_VALGRIND
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "valgrind", value))
+ return false;
+
+#ifdef JS_OOM_DO_BACKTRACES
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "oom-backtraces", value))
+ return false;
+
+#ifdef ENABLE_BINARYDATA
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "binary-data", value))
+ return false;
+
+#ifdef EXPOSE_INTL_API
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "intl-api", value))
+ return false;
+
+#if defined(SOLARIS)
+ value = BooleanValue(false);
+#else
+ value = BooleanValue(true);
+#endif
+ if (!JS_SetProperty(cx, info, "mapped-array-buffer", value))
+ return false;
+
+#ifdef MOZ_MEMORY
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "moz-memory", value))
+ return false;
+
+ value.setInt32(sizeof(void*));
+ if (!JS_SetProperty(cx, info, "pointer-byte-size", value))
+ return false;
+
+ args.rval().setObject(*info);
+ return true;
+}
+
+static bool
+GC(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ /*
+ * If the first argument is 'zone', we collect any zones previously
+ * scheduled for GC via schedulegc. If the first argument is an object, we
+ * collect the object's zone (and any other zones scheduled for
+ * GC). Otherwise, we collect all zones.
+ */
+ bool zone = false;
+ if (args.length() >= 1) {
+ Value arg = args[0];
+ if (arg.isString()) {
+ if (!JS_StringEqualsAscii(cx, arg.toString(), "zone", &zone))
+ return false;
+ } else if (arg.isObject()) {
+ PrepareZoneForGC(UncheckedUnwrap(&arg.toObject())->zone());
+ zone = true;
+ }
+ }
+
+ bool shrinking = false;
+ if (args.length() >= 2) {
+ Value arg = args[1];
+ if (arg.isString()) {
+ if (!JS_StringEqualsAscii(cx, arg.toString(), "shrinking", &shrinking))
+ return false;
+ }
+ }
+
+#ifndef JS_MORE_DETERMINISTIC
+ size_t preBytes = cx->runtime()->gc.usage.gcBytes();
+#endif
+
+ if (zone)
+ PrepareForDebugGC(cx->runtime());
+ else
+ JS::PrepareForFullGC(cx);
+
+ JSGCInvocationKind gckind = shrinking ? GC_SHRINK : GC_NORMAL;
+ JS::GCForReason(cx, gckind, JS::gcreason::API);
+
+ char buf[256] = { '\0' };
+#ifndef JS_MORE_DETERMINISTIC
+ SprintfLiteral(buf, "before %" PRIuSIZE ", after %" PRIuSIZE "\n",
+ preBytes, cx->runtime()->gc.usage.gcBytes());
+#endif
+ JSString* str = JS_NewStringCopyZ(cx, buf);
+ if (!str)
+ return false;
+ args.rval().setString(str);
+ return true;
+}
+
+static bool
+MinorGC(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.get(0) == BooleanValue(true))
+ cx->runtime()->gc.storeBuffer.setAboutToOverflow();
+
+ cx->minorGC(JS::gcreason::API);
+ args.rval().setUndefined();
+ return true;
+}
+
+#define FOR_EACH_GC_PARAM(_) \
+ _("maxBytes", JSGC_MAX_BYTES, true) \
+ _("maxMallocBytes", JSGC_MAX_MALLOC_BYTES, true) \
+ _("gcBytes", JSGC_BYTES, false) \
+ _("gcNumber", JSGC_NUMBER, false) \
+ _("mode", JSGC_MODE, true) \
+ _("unusedChunks", JSGC_UNUSED_CHUNKS, false) \
+ _("totalChunks", JSGC_TOTAL_CHUNKS, false) \
+ _("sliceTimeBudget", JSGC_SLICE_TIME_BUDGET, true) \
+ _("markStackLimit", JSGC_MARK_STACK_LIMIT, true) \
+ _("highFrequencyTimeLimit", JSGC_HIGH_FREQUENCY_TIME_LIMIT, true) \
+ _("highFrequencyLowLimit", JSGC_HIGH_FREQUENCY_LOW_LIMIT, true) \
+ _("highFrequencyHighLimit", JSGC_HIGH_FREQUENCY_HIGH_LIMIT, true) \
+ _("highFrequencyHeapGrowthMax", JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX, true) \
+ _("highFrequencyHeapGrowthMin", JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN, true) \
+ _("lowFrequencyHeapGrowth", JSGC_LOW_FREQUENCY_HEAP_GROWTH, true) \
+ _("dynamicHeapGrowth", JSGC_DYNAMIC_HEAP_GROWTH, true) \
+ _("dynamicMarkSlice", JSGC_DYNAMIC_MARK_SLICE, true) \
+ _("allocationThreshold", JSGC_ALLOCATION_THRESHOLD, true) \
+ _("minEmptyChunkCount", JSGC_MIN_EMPTY_CHUNK_COUNT, true) \
+ _("maxEmptyChunkCount", JSGC_MAX_EMPTY_CHUNK_COUNT, true) \
+ _("compactingEnabled", JSGC_COMPACTING_ENABLED, true) \
+ _("refreshFrameSlicesEnabled", JSGC_REFRESH_FRAME_SLICES_ENABLED, true)
+
+static const struct ParamInfo {
+ const char* name;
+ JSGCParamKey param;
+ bool writable;
+} paramMap[] = {
+#define DEFINE_PARAM_INFO(name, key, writable) \
+ {name, key, writable},
+FOR_EACH_GC_PARAM(DEFINE_PARAM_INFO)
+#undef DEFINE_PARAM_INFO
+};
+
+#define PARAM_NAME_LIST_ENTRY(name, key, writable) \
+ " " name
+#define GC_PARAMETER_ARGS_LIST FOR_EACH_GC_PARAM(PARAM_NAME_LIST_ENTRY)
+
+static bool
+GCParameter(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ JSString* str = ToString(cx, args.get(0));
+ if (!str)
+ return false;
+
+ JSFlatString* flatStr = JS_FlattenString(cx, str);
+ if (!flatStr)
+ return false;
+
+ size_t paramIndex = 0;
+ for (;; paramIndex++) {
+ if (paramIndex == ArrayLength(paramMap)) {
+ JS_ReportErrorASCII(cx,
+ "the first argument must be one of:" GC_PARAMETER_ARGS_LIST);
+ return false;
+ }
+ if (JS_FlatStringEqualsAscii(flatStr, paramMap[paramIndex].name))
+ break;
+ }
+ const ParamInfo& info = paramMap[paramIndex];
+ JSGCParamKey param = info.param;
+
+ // Request mode.
+ if (args.length() == 1) {
+ uint32_t value = JS_GetGCParameter(cx, param);
+ args.rval().setNumber(value);
+ return true;
+ }
+
+ if (!info.writable) {
+ JS_ReportErrorASCII(cx, "Attempt to change read-only parameter %s", info.name);
+ return false;
+ }
+
+ if (disableOOMFunctions && (param == JSGC_MAX_BYTES || param == JSGC_MAX_MALLOC_BYTES)) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ double d;
+ if (!ToNumber(cx, args[1], &d))
+ return false;
+
+ if (d < 0 || d > UINT32_MAX) {
+ JS_ReportErrorASCII(cx, "Parameter value out of range");
+ return false;
+ }
+
+ uint32_t value = floor(d);
+ if (param == JSGC_MARK_STACK_LIMIT && JS::IsIncrementalGCInProgress(cx)) {
+ JS_ReportErrorASCII(cx, "attempt to set markStackLimit while a GC is in progress");
+ return false;
+ }
+
+ if (param == JSGC_MAX_BYTES) {
+ uint32_t gcBytes = JS_GetGCParameter(cx, JSGC_BYTES);
+ if (value < gcBytes) {
+ JS_ReportErrorASCII(cx,
+ "attempt to set maxBytes to the value less than the current "
+ "gcBytes (%u)",
+ gcBytes);
+ return false;
+ }
+ }
+
+ bool ok;
+ {
+ JSRuntime* rt = cx->runtime();
+ AutoLockGC lock(rt);
+ ok = rt->gc.setParameter(param, value, lock);
+ }
+
+ if (!ok) {
+ JS_ReportErrorASCII(cx, "Parameter value out of range");
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static void
+SetAllowRelazification(JSContext* cx, bool allow)
+{
+ JSRuntime* rt = cx->runtime();
+ MOZ_ASSERT(rt->allowRelazificationForTesting != allow);
+ rt->allowRelazificationForTesting = allow;
+
+ for (AllScriptFramesIter i(cx); !i.done(); ++i)
+ i.script()->setDoNotRelazify(allow);
+}
+
+static bool
+RelazifyFunctions(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Relazifying functions on GC is usually only done for compartments that are
+ // not active. To aid fuzzing, this testing function allows us to relazify
+ // even if the compartment is active.
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ SetAllowRelazification(cx, true);
+
+ JS::PrepareForFullGC(cx);
+ JS::GCForReason(cx, GC_SHRINK, JS::gcreason::API);
+
+ SetAllowRelazification(cx, false);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+IsProxy(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "the function takes exactly one argument");
+ return false;
+ }
+ if (!args[0].isObject()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+ args.rval().setBoolean(args[0].toObject().is<ProxyObject>());
+ return true;
+}
+
+static bool
+WasmIsSupported(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(wasm::HasSupport(cx));
+ return true;
+}
+
+static bool
+WasmTextToBinary(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (!args.requireAtLeast(cx, "wasmTextToBinary", 1))
+ return false;
+
+ if (!args[0].isString()) {
+ ReportUsageErrorASCII(cx, callee, "First argument must be a String");
+ return false;
+ }
+
+ AutoStableStringChars twoByteChars(cx);
+ if (!twoByteChars.initTwoByte(cx, args[0].toString()))
+ return false;
+
+ if (args.hasDefined(1)) {
+ if (!args[1].isString()) {
+ ReportUsageErrorASCII(cx, callee, "Second argument, if present, must be a String");
+ return false;
+ }
+ }
+
+ wasm::Bytes bytes;
+ UniqueChars error;
+ if (!wasm::TextToBinary(twoByteChars.twoByteChars(), &bytes, &error)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_TEXT_FAIL,
+ error.get() ? error.get() : "out of memory");
+ return false;
+ }
+
+ RootedObject obj(cx, JS_NewUint8Array(cx, bytes.length()));
+ if (!obj)
+ return false;
+
+ memcpy(obj->as<TypedArrayObject>().viewDataUnshared(), bytes.begin(), bytes.length());
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool
+WasmBinaryToText(JSContext* cx, unsigned argc, Value* vp)
+{
+ MOZ_ASSERT(cx->options().wasm());
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.get(0).isObject() || !args.get(0).toObject().is<TypedArrayObject>()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_ARG);
+ return false;
+ }
+
+ Rooted<TypedArrayObject*> code(cx, &args[0].toObject().as<TypedArrayObject>());
+
+ if (!TypedArrayObject::ensureHasBuffer(cx, code))
+ return false;
+
+ if (code->isSharedMemory()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_ARG);
+ return false;
+ }
+
+ const uint8_t* bufferStart = code->bufferUnshared()->dataPointer();
+ const uint8_t* bytes = bufferStart + code->byteOffset();
+ uint32_t length = code->byteLength();
+
+ Vector<uint8_t> copy(cx);
+ if (code->bufferUnshared()->hasInlineData()) {
+ if (!copy.append(bytes, length))
+ return false;
+ bytes = copy.begin();
+ }
+
+ bool experimental = false;
+ if (args.length() > 1) {
+ JSString* opt = JS::ToString(cx, args[1]);
+ if (!opt)
+ return false;
+ bool match;
+ if (!JS_StringEqualsAscii(cx, opt, "experimental", &match))
+ return false;
+ experimental = match;
+ }
+
+ StringBuffer buffer(cx);
+ bool ok;
+ if (experimental)
+ ok = wasm::BinaryToExperimentalText(cx, bytes, length, buffer, wasm::ExperimentalTextFormatting());
+ else
+ ok = wasm::BinaryToText(cx, bytes, length, buffer);
+ if (!ok) {
+ if (!cx->isExceptionPending())
+ JS_ReportErrorASCII(cx, "wasm binary to text print error");
+ return false;
+ }
+
+ JSString* result = buffer.finishString();
+ if (!result)
+ return false;
+
+ args.rval().setString(result);
+ return true;
+}
+
+static bool
+WasmExtractCode(JSContext* cx, unsigned argc, Value* vp)
+{
+ MOZ_ASSERT(cx->options().wasm());
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.get(0).isObject()) {
+ JS_ReportErrorASCII(cx, "argument is not an object");
+ return false;
+ }
+
+ JSObject* unwrapped = CheckedUnwrap(&args.get(0).toObject());
+ if (!unwrapped || !unwrapped->is<WasmModuleObject>()) {
+ JS_ReportErrorASCII(cx, "argument is not a WebAssembly.Module");
+ return false;
+ }
+
+ Rooted<WasmModuleObject*> module(cx, &unwrapped->as<WasmModuleObject>());
+ RootedValue result(cx);
+ if (!module->module().extractCode(cx, &result))
+ return false;
+
+ args.rval().set(result);
+ return true;
+}
+
+static bool
+IsLazyFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "The function takes exactly one argument.");
+ return false;
+ }
+ if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+ JS_ReportErrorASCII(cx, "The first argument should be a function.");
+ return false;
+ }
+ args.rval().setBoolean(args[0].toObject().as<JSFunction>().isInterpretedLazy());
+ return true;
+}
+
+static bool
+IsRelazifiableFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "The function takes exactly one argument.");
+ return false;
+ }
+ if (!args[0].isObject() ||
+ !args[0].toObject().is<JSFunction>())
+ {
+ JS_ReportErrorASCII(cx, "The first argument should be a function.");
+ return false;
+ }
+
+ JSFunction* fun = &args[0].toObject().as<JSFunction>();
+ args.rval().setBoolean(fun->hasScript() && fun->nonLazyScript()->isRelazifiable());
+ return true;
+}
+
+static bool
+InternalConst(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() == 0) {
+ JS_ReportErrorASCII(cx, "the function takes exactly one argument");
+ return false;
+ }
+
+ JSString* str = ToString(cx, args[0]);
+ if (!str)
+ return false;
+ JSFlatString* flat = JS_FlattenString(cx, str);
+ if (!flat)
+ return false;
+
+ if (JS_FlatStringEqualsAscii(flat, "INCREMENTAL_MARK_STACK_BASE_CAPACITY")) {
+ args.rval().setNumber(uint32_t(js::INCREMENTAL_MARK_STACK_BASE_CAPACITY));
+ } else {
+ JS_ReportErrorASCII(cx, "unknown const name");
+ return false;
+ }
+ return true;
+}
+
+static bool
+GCPreserveCode(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 0) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ cx->runtime()->gc.setAlwaysPreserveCode();
+
+ args.rval().setUndefined();
+ return true;
+}
+
+#ifdef JS_GC_ZEAL
+static bool
+GCZeal(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() > 2) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Too many arguments");
+ return false;
+ }
+
+ uint32_t zeal;
+ if (!ToUint32(cx, args.get(0), &zeal))
+ return false;
+
+ if (zeal > uint32_t(gc::ZealMode::Limit)) {
+ JS_ReportErrorASCII(cx, "gczeal argument out of range");
+ return false;
+ }
+
+ uint32_t frequency = JS_DEFAULT_ZEAL_FREQ;
+ if (args.length() >= 2) {
+ if (!ToUint32(cx, args.get(1), &frequency))
+ return false;
+ }
+
+ JS_SetGCZeal(cx, (uint8_t)zeal, frequency);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ScheduleGC(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() > 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Too many arguments");
+ return false;
+ }
+
+ if (args.length() == 0) {
+ /* Fetch next zeal trigger only. */
+ } else if (args[0].isInt32()) {
+ /* Schedule a GC to happen after |arg| allocations. */
+ JS_ScheduleGC(cx, args[0].toInt32());
+ } else if (args[0].isObject()) {
+ /* Ensure that |zone| is collected during the next GC. */
+ Zone* zone = UncheckedUnwrap(&args[0].toObject())->zone();
+ PrepareZoneForGC(zone);
+ } else if (args[0].isString()) {
+ /* This allows us to schedule the atoms zone for GC. */
+ Zone* zone = args[0].toString()->zoneFromAnyThread();
+ if (!CurrentThreadCanAccessZone(zone)) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Specified zone not accessible for GC");
+ return false;
+ }
+ PrepareZoneForGC(zone);
+ } else {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Bad argument - expecting integer, object or string");
+ return false;
+ }
+
+ uint32_t zealBits;
+ uint32_t freq;
+ uint32_t next;
+ JS_GetGCZealBits(cx, &zealBits, &freq, &next);
+ args.rval().setInt32(next);
+ return true;
+}
+
+static bool
+SelectForGC(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ /*
+ * The selectedForMarking set is intended to be manually marked at slice
+ * start to detect missing pre-barriers. It is invalid for nursery things
+ * to be in the set, so evict the nursery before adding items.
+ */
+ JSRuntime* rt = cx->runtime();
+ rt->gc.evictNursery();
+
+ for (unsigned i = 0; i < args.length(); i++) {
+ if (args[i].isObject()) {
+ if (!rt->gc.selectForMarking(&args[i].toObject()))
+ return false;
+ }
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+VerifyPreBarriers(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() > 0) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Too many arguments");
+ return false;
+ }
+
+ gc::VerifyBarriers(cx->runtime(), gc::PreBarrierVerifier);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+VerifyPostBarriers(JSContext* cx, unsigned argc, Value* vp)
+{
+ // This is a no-op since the post barrier verifier was removed.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Too many arguments");
+ return false;
+ }
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+GCState(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 0) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Too many arguments");
+ return false;
+ }
+
+ const char* state = StateName(cx->runtime()->gc.state());
+ JSString* str = JS_NewStringCopyZ(cx, state);
+ if (!str)
+ return false;
+ args.rval().setString(str);
+ return true;
+}
+
+static bool
+DeterministicGC(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ cx->runtime()->gc.setDeterministic(ToBoolean(args[0]));
+ args.rval().setUndefined();
+ return true;
+}
+#endif /* JS_GC_ZEAL */
+
+static bool
+StartGC(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() > 2) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ auto budget = SliceBudget::unlimited();
+ if (args.length() >= 1) {
+ uint32_t work = 0;
+ if (!ToUint32(cx, args[0], &work))
+ return false;
+ budget = SliceBudget(WorkBudget(work));
+ }
+
+ bool shrinking = false;
+ if (args.length() >= 2) {
+ Value arg = args[1];
+ if (arg.isString()) {
+ if (!JS_StringEqualsAscii(cx, arg.toString(), "shrinking", &shrinking))
+ return false;
+ }
+ }
+
+ JSRuntime* rt = cx->runtime();
+ if (rt->gc.isIncrementalGCInProgress()) {
+ RootedObject callee(cx, &args.callee());
+ JS_ReportErrorASCII(cx, "Incremental GC already in progress");
+ return false;
+ }
+
+ JSGCInvocationKind gckind = shrinking ? GC_SHRINK : GC_NORMAL;
+ rt->gc.startDebugGC(gckind, budget);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+GCSlice(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() > 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ auto budget = SliceBudget::unlimited();
+ if (args.length() == 1) {
+ uint32_t work = 0;
+ if (!ToUint32(cx, args[0], &work))
+ return false;
+ budget = SliceBudget(WorkBudget(work));
+ }
+
+ JSRuntime* rt = cx->runtime();
+ if (!rt->gc.isIncrementalGCInProgress())
+ rt->gc.startDebugGC(GC_NORMAL, budget);
+ else
+ rt->gc.debugGCSlice(budget);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+AbortGC(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 0) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ cx->runtime()->gc.abortGC();
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+FullCompartmentChecks(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ cx->runtime()->gc.setFullCompartmentChecks(ToBoolean(args[0]));
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+NondeterministicGetWeakMapKeys(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+ if (!args[0].isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
+ "nondeterministicGetWeakMapKeys", "WeakMap",
+ InformalValueTypeName(args[0]));
+ return false;
+ }
+ RootedObject arr(cx);
+ RootedObject mapObj(cx, &args[0].toObject());
+ if (!JS_NondeterministicGetWeakMapKeys(cx, mapObj, &arr))
+ return false;
+ if (!arr) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
+ "nondeterministicGetWeakMapKeys", "WeakMap",
+ args[0].toObject().getClass()->name);
+ return false;
+ }
+ args.rval().setObject(*arr);
+ return true;
+}
+
+class HasChildTracer : public JS::CallbackTracer
+{
+ RootedValue child_;
+ bool found_;
+
+ void onChild(const JS::GCCellPtr& thing) override {
+ if (thing.asCell() == child_.toGCThing())
+ found_ = true;
+ }
+
+ public:
+ HasChildTracer(JSContext* cx, HandleValue child)
+ : JS::CallbackTracer(cx, TraceWeakMapKeysValues), child_(cx, child), found_(false)
+ {}
+
+ bool found() const { return found_; }
+};
+
+static bool
+HasChild(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedValue parent(cx, args.get(0));
+ RootedValue child(cx, args.get(1));
+
+ if (!parent.isMarkable() || !child.isMarkable()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ HasChildTracer trc(cx, child);
+ TraceChildren(&trc, parent.toGCThing(), parent.traceKind());
+ args.rval().setBoolean(trc.found());
+ return true;
+}
+
+static bool
+SetSavedStacksRNGState(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "setSavedStacksRNGState", 1))
+ return false;
+
+ int32_t seed;
+ if (!ToInt32(cx, args[0], &seed))
+ return false;
+
+ // Either one or the other of the seed arguments must be non-zero;
+ // make this true no matter what value 'seed' has.
+ cx->compartment()->savedStacks().setRNGState(seed, (seed + 1) * 33);
+ return true;
+}
+
+static bool
+GetSavedFrameCount(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setNumber(cx->compartment()->savedStacks().count());
+ return true;
+}
+
+static bool
+SaveStack(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ JS::StackCapture capture((JS::AllFrames()));
+ if (args.length() >= 1) {
+ double maxDouble;
+ if (!ToNumber(cx, args[0], &maxDouble))
+ return false;
+ if (mozilla::IsNaN(maxDouble) || maxDouble < 0 || maxDouble > UINT32_MAX) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[0], nullptr,
+ "not a valid maximum frame count", NULL);
+ return false;
+ }
+ uint32_t max = uint32_t(maxDouble);
+ if (max > 0)
+ capture = JS::StackCapture(JS::MaxFrames(max));
+ }
+
+ JSCompartment* targetCompartment = cx->compartment();
+ if (args.length() >= 2) {
+ if (!args[1].isObject()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[0], nullptr,
+ "not an object", NULL);
+ return false;
+ }
+ RootedObject obj(cx, UncheckedUnwrap(&args[1].toObject()));
+ if (!obj)
+ return false;
+ targetCompartment = obj->compartment();
+ }
+
+ RootedObject stack(cx);
+ {
+ AutoCompartment ac(cx, targetCompartment);
+ if (!JS::CaptureCurrentStack(cx, &stack, mozilla::Move(capture)))
+ return false;
+ }
+
+ if (stack && !cx->compartment()->wrap(cx, &stack))
+ return false;
+
+ args.rval().setObjectOrNull(stack);
+ return true;
+}
+
+static bool
+CaptureFirstSubsumedFrame(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "captureFirstSubsumedFrame", 1))
+ return false;
+
+ if (!args[0].isObject()) {
+ JS_ReportErrorASCII(cx, "The argument must be an object");
+ return false;
+ }
+
+ RootedObject obj(cx, &args[0].toObject());
+ obj = CheckedUnwrap(obj);
+ if (!obj) {
+ JS_ReportErrorASCII(cx, "Denied permission to object.");
+ return false;
+ }
+
+ JS::StackCapture capture(JS::FirstSubsumedFrame(cx, obj->compartment()->principals()));
+ if (args.length() > 1)
+ capture.as<JS::FirstSubsumedFrame>().ignoreSelfHosted = JS::ToBoolean(args[1]);
+
+ JS::RootedObject capturedStack(cx);
+ if (!JS::CaptureCurrentStack(cx, &capturedStack, mozilla::Move(capture)))
+ return false;
+
+ args.rval().setObjectOrNull(capturedStack);
+ return true;
+}
+
+static bool
+CallFunctionFromNativeFrame(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "The function takes exactly one argument.");
+ return false;
+ }
+ if (!args[0].isObject() || !IsCallable(args[0])) {
+ JS_ReportErrorASCII(cx, "The first argument should be a function.");
+ return false;
+ }
+
+ RootedObject function(cx, &args[0].toObject());
+ return Call(cx, UndefinedHandleValue, function,
+ JS::HandleValueArray::empty(), args.rval());
+}
+
+static bool
+CallFunctionWithAsyncStack(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 3) {
+ JS_ReportErrorASCII(cx, "The function takes exactly three arguments.");
+ return false;
+ }
+ if (!args[0].isObject() || !IsCallable(args[0])) {
+ JS_ReportErrorASCII(cx, "The first argument should be a function.");
+ return false;
+ }
+ if (!args[1].isObject() || !args[1].toObject().is<SavedFrame>()) {
+ JS_ReportErrorASCII(cx, "The second argument should be a SavedFrame.");
+ return false;
+ }
+ if (!args[2].isString() || args[2].toString()->empty()) {
+ JS_ReportErrorASCII(cx, "The third argument should be a non-empty string.");
+ return false;
+ }
+
+ RootedObject function(cx, &args[0].toObject());
+ RootedObject stack(cx, &args[1].toObject());
+ RootedString asyncCause(cx, args[2].toString());
+ JSAutoByteString utf8Cause;
+ if (!utf8Cause.encodeUtf8(cx, asyncCause)) {
+ MOZ_ASSERT(cx->isExceptionPending());
+ return false;
+ }
+
+ JS::AutoSetAsyncStackForNewCalls sas(cx, stack, utf8Cause.ptr(),
+ JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT);
+ return Call(cx, UndefinedHandleValue, function,
+ JS::HandleValueArray::empty(), args.rval());
+}
+
+static bool
+EnableTrackAllocations(JSContext* cx, unsigned argc, Value* vp)
+{
+ SetAllocationMetadataBuilder(cx, &SavedStacks::metadataBuilder);
+ return true;
+}
+
+static bool
+DisableTrackAllocations(JSContext* cx, unsigned argc, Value* vp)
+{
+ SetAllocationMetadataBuilder(cx, nullptr);
+ return true;
+}
+
+static void
+FinalizeExternalString(Zone* zone, const JSStringFinalizer* fin, char16_t* chars);
+
+static const JSStringFinalizer ExternalStringFinalizer =
+ { FinalizeExternalString };
+
+static void
+FinalizeExternalString(Zone* zone, const JSStringFinalizer* fin, char16_t* chars)
+{
+ MOZ_ASSERT(fin == &ExternalStringFinalizer);
+ js_free(chars);
+}
+
+static bool
+NewExternalString(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1 || !args[0].isString()) {
+ JS_ReportErrorASCII(cx, "newExternalString takes exactly one string argument.");
+ return false;
+ }
+
+ RootedString str(cx, args[0].toString());
+ size_t len = str->length();
+
+ UniqueTwoByteChars buf(cx->pod_malloc<char16_t>(len));
+ if (!buf)
+ return false;
+
+ if (!JS_CopyStringChars(cx, mozilla::Range<char16_t>(buf.get(), len), str))
+ return false;
+
+ JSString* res = JS_NewExternalString(cx, buf.get(), len, &ExternalStringFinalizer);
+ if (!res)
+ return false;
+
+ mozilla::Unused << buf.release();
+ args.rval().setString(res);
+ return true;
+}
+
+static bool
+EnsureFlatString(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1 || !args[0].isString()) {
+ JS_ReportErrorASCII(cx, "ensureFlatString takes exactly one string argument.");
+ return false;
+ }
+
+ JSFlatString* flat = args[0].toString()->ensureFlat(cx);
+ if (!flat)
+ return false;
+
+ args.rval().setString(flat);
+ return true;
+}
+
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+static bool
+OOMThreadTypes(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setInt32(js::oom::THREAD_TYPE_MAX);
+ return true;
+}
+
+static bool
+SetupOOMFailure(JSContext* cx, bool failAlways, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (disableOOMFunctions) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ if (args.length() < 1) {
+ JS_ReportErrorASCII(cx, "Count argument required");
+ return false;
+ }
+
+ if (args.length() > 2) {
+ JS_ReportErrorASCII(cx, "Too many arguments");
+ return false;
+ }
+
+ int32_t count;
+ if (!JS::ToInt32(cx, args.get(0), &count))
+ return false;
+
+ if (count <= 0) {
+ JS_ReportErrorASCII(cx, "OOM cutoff should be positive");
+ return false;
+ }
+
+ uint32_t targetThread = js::oom::THREAD_TYPE_MAIN;
+ if (args.length() > 1 && !ToUint32(cx, args[1], &targetThread))
+ return false;
+
+ if (targetThread == js::oom::THREAD_TYPE_NONE || targetThread >= js::oom::THREAD_TYPE_MAX) {
+ JS_ReportErrorASCII(cx, "Invalid thread type specified");
+ return false;
+ }
+
+ HelperThreadState().waitForAllThreads();
+ js::oom::SimulateOOMAfter(count, targetThread, failAlways);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+OOMAfterAllocations(JSContext* cx, unsigned argc, Value* vp)
+{
+ return SetupOOMFailure(cx, true, argc, vp);
+}
+
+static bool
+OOMAtAllocation(JSContext* cx, unsigned argc, Value* vp)
+{
+ return SetupOOMFailure(cx, false, argc, vp);
+}
+
+static bool
+ResetOOMFailure(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(js::oom::HadSimulatedOOM());
+ js::oom::ResetSimulatedOOM();
+ return true;
+}
+
+static bool
+OOMTest(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 1 || args.length() > 2) {
+ JS_ReportErrorASCII(cx, "oomTest() takes between 1 and 2 arguments.");
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+ JS_ReportErrorASCII(cx, "The first argument to oomTest() must be a function.");
+ return false;
+ }
+
+ if (args.length() == 2 && !args[1].isBoolean()) {
+ JS_ReportErrorASCII(cx, "The optional second argument to oomTest() must be a boolean.");
+ return false;
+ }
+
+ bool expectExceptionOnFailure = true;
+ if (args.length() == 2)
+ expectExceptionOnFailure = args[1].toBoolean();
+
+ // There are some places where we do fail without raising an exception, so
+ // we can't expose this to the fuzzers by default.
+ if (fuzzingSafe)
+ expectExceptionOnFailure = false;
+
+ if (disableOOMFunctions) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ RootedFunction function(cx, &args[0].toObject().as<JSFunction>());
+
+ bool verbose = EnvVarIsDefined("OOM_VERBOSE");
+
+ unsigned threadStart = oom::THREAD_TYPE_MAIN;
+ unsigned threadEnd = oom::THREAD_TYPE_MAX;
+
+ // Test a single thread type if specified by the OOM_THREAD environment variable.
+ int threadOption = 0;
+ if (EnvVarAsInt("OOM_THREAD", &threadOption)) {
+ if (threadOption < oom::THREAD_TYPE_MAIN || threadOption > oom::THREAD_TYPE_MAX) {
+ JS_ReportErrorASCII(cx, "OOM_THREAD value out of range.");
+ return false;
+ }
+
+ threadStart = threadOption;
+ threadEnd = threadOption + 1;
+ }
+
+ JSRuntime* rt = cx->runtime();
+ if (rt->runningOOMTest) {
+ JS_ReportErrorASCII(cx, "Nested call to oomTest() is not allowed.");
+ return false;
+ }
+ rt->runningOOMTest = true;
+
+ MOZ_ASSERT(!cx->isExceptionPending());
+ rt->hadOutOfMemory = false;
+
+ JS_SetGCZeal(cx, 0, JS_DEFAULT_ZEAL_FREQ);
+
+ for (unsigned thread = threadStart; thread < threadEnd; thread++) {
+ if (verbose)
+ fprintf(stderr, "thread %d\n", thread);
+
+ HelperThreadState().waitForAllThreads();
+ js::oom::targetThread = thread;
+
+ unsigned allocation = 1;
+ bool handledOOM;
+ do {
+ if (verbose)
+ fprintf(stderr, " allocation %d\n", allocation);
+
+ MOZ_ASSERT(!cx->isExceptionPending());
+ MOZ_ASSERT(!cx->runtime()->hadOutOfMemory);
+
+ js::oom::SimulateOOMAfter(allocation, thread, false);
+
+ RootedValue result(cx);
+ bool ok = JS_CallFunction(cx, cx->global(), function,
+ HandleValueArray::empty(), &result);
+
+ handledOOM = js::oom::HadSimulatedOOM();
+ js::oom::ResetSimulatedOOM();
+
+ MOZ_ASSERT_IF(ok, !cx->isExceptionPending());
+
+ if (ok) {
+ MOZ_ASSERT(!cx->isExceptionPending(),
+ "Thunk execution succeeded but an exception was raised - "
+ "missing error check?");
+ } else if (expectExceptionOnFailure) {
+ MOZ_ASSERT(cx->isExceptionPending(),
+ "Thunk execution failed but no exception was raised - "
+ "missing call to js::ReportOutOfMemory()?");
+ }
+
+ // Note that it is possible that the function throws an exception
+ // unconnected to OOM, in which case we ignore it. More correct
+ // would be to have the caller pass some kind of exception
+ // specification and to check the exception against it.
+
+ cx->clearPendingException();
+ cx->runtime()->hadOutOfMemory = false;
+
+#ifdef JS_TRACE_LOGGING
+ // Reset the TraceLogger state if enabled.
+ TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
+ if (logger->enabled()) {
+ while (logger->enabled())
+ logger->disable();
+ logger->enable(cx);
+ }
+#endif
+
+ allocation++;
+ } while (handledOOM);
+
+ if (verbose) {
+ fprintf(stderr, " finished after %d allocations\n", allocation - 2);
+ }
+ }
+
+ rt->runningOOMTest = false;
+ args.rval().setUndefined();
+ return true;
+}
+#endif
+
+#ifdef SPIDERMONKEY_PROMISE
+static bool
+SettlePromiseNow(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "settlePromiseNow", 1))
+ return false;
+ if (!args[0].isObject() || !args[0].toObject().is<PromiseObject>()) {
+ JS_ReportErrorASCII(cx, "first argument must be a Promise object");
+ return false;
+ }
+
+ RootedNativeObject promise(cx, &args[0].toObject().as<NativeObject>());
+ int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32();
+ promise->setFixedSlot(PromiseSlot_Flags,
+ Int32Value(flags | PROMISE_FLAG_RESOLVED | PROMISE_FLAG_FULFILLED));
+ promise->setFixedSlot(PromiseSlot_ReactionsOrResult, UndefinedValue());
+
+ JS::dbg::onPromiseSettled(cx, promise);
+ return true;
+}
+
+static bool
+GetWaitForAllPromise(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "getWaitForAllPromise", 1))
+ return false;
+ if (!args[0].isObject() || !IsPackedArray(&args[0].toObject())) {
+ JS_ReportErrorASCII(cx, "first argument must be a dense Array of Promise objects");
+ return false;
+ }
+ RootedNativeObject list(cx, &args[0].toObject().as<NativeObject>());
+ AutoObjectVector promises(cx);
+ uint32_t count = list->getDenseInitializedLength();
+ if (!promises.resize(count))
+ return false;
+
+ for (uint32_t i = 0; i < count; i++) {
+ RootedValue elem(cx, list->getDenseElement(i));
+ if (!elem.isObject() || !elem.toObject().is<PromiseObject>()) {
+ JS_ReportErrorASCII(cx, "Each entry in the passed-in Array must be a Promise");
+ return false;
+ }
+ promises[i].set(&elem.toObject());
+ }
+
+ RootedObject resultPromise(cx, JS::GetWaitForAllPromise(cx, promises));
+ if (!resultPromise)
+ return false;
+
+ args.rval().set(ObjectValue(*resultPromise));
+ return true;
+}
+
+static bool
+ResolvePromise(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "resolvePromise", 2))
+ return false;
+ if (!args[0].isObject() || !UncheckedUnwrap(&args[0].toObject())->is<PromiseObject>()) {
+ JS_ReportErrorASCII(cx, "first argument must be a maybe-wrapped Promise object");
+ return false;
+ }
+
+ RootedObject promise(cx, &args[0].toObject());
+ RootedValue resolution(cx, args[1]);
+ mozilla::Maybe<AutoCompartment> ac;
+ if (IsWrapper(promise)) {
+ promise = UncheckedUnwrap(promise);
+ ac.emplace(cx, promise);
+ if (!cx->compartment()->wrap(cx, &resolution))
+ return false;
+ }
+
+ bool result = JS::ResolvePromise(cx, promise, resolution);
+ if (result)
+ args.rval().setUndefined();
+ return result;
+}
+
+static bool
+RejectPromise(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "rejectPromise", 2))
+ return false;
+ if (!args[0].isObject() || !UncheckedUnwrap(&args[0].toObject())->is<PromiseObject>()) {
+ JS_ReportErrorASCII(cx, "first argument must be a maybe-wrapped Promise object");
+ return false;
+ }
+
+ RootedObject promise(cx, &args[0].toObject());
+ RootedValue reason(cx, args[1]);
+ mozilla::Maybe<AutoCompartment> ac;
+ if (IsWrapper(promise)) {
+ promise = UncheckedUnwrap(promise);
+ ac.emplace(cx, promise);
+ if (!cx->compartment()->wrap(cx, &reason))
+ return false;
+ }
+
+ bool result = JS::RejectPromise(cx, promise, reason);
+ if (result)
+ args.rval().setUndefined();
+ return result;
+}
+
+#else
+
+static const js::Class FakePromiseClass = {
+ "Promise", JSCLASS_IS_ANONYMOUS
+};
+
+static bool
+MakeFakePromise(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject obj(cx, NewObjectWithGivenProto(cx, &FakePromiseClass, nullptr));
+ if (!obj)
+ return false;
+
+ JS::dbg::onNewPromise(cx, obj);
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool
+SettleFakePromise(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "settleFakePromise", 1))
+ return false;
+ if (!args[0].isObject() || args[0].toObject().getClass() != &FakePromiseClass) {
+ JS_ReportErrorASCII(cx, "first argument must be a (fake) Promise object");
+ return false;
+ }
+
+ RootedObject promise(cx, &args[0].toObject());
+ JS::dbg::onPromiseSettled(cx, promise);
+ return true;
+}
+#endif // SPIDERMONKEY_PROMISE
+
+static unsigned finalizeCount = 0;
+
+static void
+finalize_counter_finalize(JSFreeOp* fop, JSObject* obj)
+{
+ ++finalizeCount;
+}
+
+static const JSClassOps FinalizeCounterClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ finalize_counter_finalize
+};
+
+static const JSClass FinalizeCounterClass = {
+ "FinalizeCounter",
+ JSCLASS_IS_ANONYMOUS |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &FinalizeCounterClassOps
+};
+
+static bool
+MakeFinalizeObserver(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ JSObject* obj = JS_NewObjectWithGivenProto(cx, &FinalizeCounterClass, nullptr);
+ if (!obj)
+ return false;
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool
+FinalizeCount(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setInt32(finalizeCount);
+ return true;
+}
+
+static bool
+ResetFinalizeCount(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ finalizeCount = 0;
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+DumpHeap(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ DumpHeapNurseryBehaviour nurseryBehaviour = js::IgnoreNurseryObjects;
+ FILE* dumpFile = nullptr;
+
+ unsigned i = 0;
+ if (args.length() > i) {
+ Value v = args[i];
+ if (v.isString()) {
+ JSString* str = v.toString();
+ bool same = false;
+ if (!JS_StringEqualsAscii(cx, str, "collectNurseryBeforeDump", &same))
+ return false;
+ if (same) {
+ nurseryBehaviour = js::CollectNurseryBeforeDump;
+ ++i;
+ }
+ }
+ }
+
+ if (args.length() > i) {
+ Value v = args[i];
+ if (v.isString()) {
+ if (!fuzzingSafe) {
+ RootedString str(cx, v.toString());
+ JSAutoByteString fileNameBytes;
+ if (!fileNameBytes.encodeLatin1(cx, str))
+ return false;
+ const char* fileName = fileNameBytes.ptr();
+ dumpFile = fopen(fileName, "w");
+ if (!dumpFile) {
+ fileNameBytes.clear();
+ if (!fileNameBytes.encodeUtf8(cx, str))
+ return false;
+ JS_ReportErrorUTF8(cx, "can't open %s", fileNameBytes.ptr());
+ return false;
+ }
+ }
+ ++i;
+ }
+ }
+
+ if (i != args.length()) {
+ JS_ReportErrorASCII(cx, "bad arguments passed to dumpHeap");
+ if (dumpFile)
+ fclose(dumpFile);
+ return false;
+ }
+
+ js::DumpHeap(cx, dumpFile ? dumpFile : stdout, nurseryBehaviour);
+
+ if (dumpFile)
+ fclose(dumpFile);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+Terminate(JSContext* cx, unsigned arg, Value* vp)
+{
+#ifdef JS_MORE_DETERMINISTIC
+ // Print a message to stderr in more-deterministic builds to help jsfunfuzz
+ // find uncatchable-exception bugs.
+ fprintf(stderr, "terminate called\n");
+#endif
+
+ JS_ClearPendingException(cx);
+ return false;
+}
+
+static bool
+ReadSPSProfilingStack(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setUndefined();
+
+ // Return boolean 'false' if profiler is not enabled.
+ if (!cx->runtime()->spsProfiler.enabled()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ // Array holding physical jit stack frames.
+ RootedObject stack(cx, NewDenseEmptyArray(cx));
+ if (!stack)
+ return false;
+
+ // If profiler sampling has been suppressed, return an empty
+ // stack.
+ if (!cx->runtime()->isProfilerSamplingEnabled()) {
+ args.rval().setObject(*stack);
+ return true;
+ }
+
+ struct InlineFrameInfo
+ {
+ InlineFrameInfo(const char* kind, UniqueChars&& label)
+ : kind(kind), label(mozilla::Move(label)) {}
+ const char* kind;
+ UniqueChars label;
+ };
+
+ Vector<Vector<InlineFrameInfo, 0, TempAllocPolicy>, 0, TempAllocPolicy> frameInfo(cx);
+
+ JS::ProfilingFrameIterator::RegisterState state;
+ for (JS::ProfilingFrameIterator i(cx, state); !i.done(); ++i) {
+ MOZ_ASSERT(i.stackAddress() != nullptr);
+
+ if (!frameInfo.emplaceBack(cx))
+ return false;
+
+ const size_t MaxInlineFrames = 16;
+ JS::ProfilingFrameIterator::Frame frames[MaxInlineFrames];
+ uint32_t nframes = i.extractStack(frames, 0, MaxInlineFrames);
+ MOZ_ASSERT(nframes <= MaxInlineFrames);
+ for (uint32_t i = 0; i < nframes; i++) {
+ const char* frameKindStr = nullptr;
+ switch (frames[i].kind) {
+ case JS::ProfilingFrameIterator::Frame_Baseline:
+ frameKindStr = "baseline";
+ break;
+ case JS::ProfilingFrameIterator::Frame_Ion:
+ frameKindStr = "ion";
+ break;
+ case JS::ProfilingFrameIterator::Frame_Wasm:
+ frameKindStr = "wasm";
+ break;
+ default:
+ frameKindStr = "unknown";
+ }
+
+ if (!frameInfo.back().emplaceBack(frameKindStr, mozilla::Move(frames[i].label)))
+ return false;
+ }
+ }
+
+ RootedObject inlineFrameInfo(cx);
+ RootedString frameKind(cx);
+ RootedString frameLabel(cx);
+ RootedId idx(cx);
+
+ const unsigned propAttrs = JSPROP_ENUMERATE;
+
+ uint32_t physicalFrameNo = 0;
+ for (auto& frame : frameInfo) {
+ // Array holding all inline frames in a single physical jit stack frame.
+ RootedObject inlineStack(cx, NewDenseEmptyArray(cx));
+ if (!inlineStack)
+ return false;
+
+ uint32_t inlineFrameNo = 0;
+ for (auto& inlineFrame : frame) {
+ // Object holding frame info.
+ RootedObject inlineFrameInfo(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!inlineFrameInfo)
+ return false;
+
+ frameKind = NewStringCopyZ<CanGC>(cx, inlineFrame.kind);
+ if (!frameKind)
+ return false;
+
+ if (!JS_DefineProperty(cx, inlineFrameInfo, "kind", frameKind, propAttrs))
+ return false;
+
+ auto chars = inlineFrame.label.release();
+ frameLabel = NewString<CanGC>(cx, reinterpret_cast<Latin1Char*>(chars), strlen(chars));
+ if (!frameLabel)
+ return false;
+
+ if (!JS_DefineProperty(cx, inlineFrameInfo, "label", frameLabel, propAttrs))
+ return false;
+
+ idx = INT_TO_JSID(inlineFrameNo);
+ if (!JS_DefinePropertyById(cx, inlineStack, idx, inlineFrameInfo, 0))
+ return false;
+
+ ++inlineFrameNo;
+ }
+
+ // Push inline array into main array.
+ idx = INT_TO_JSID(physicalFrameNo);
+ if (!JS_DefinePropertyById(cx, stack, idx, inlineStack, 0))
+ return false;
+
+ ++physicalFrameNo;
+ }
+
+ args.rval().setObject(*stack);
+ return true;
+}
+
+static bool
+EnableOsiPointRegisterChecks(JSContext*, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+#ifdef CHECK_OSIPOINT_REGISTERS
+ jit::JitOptions.checkOsiPointRegisters = true;
+#endif
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+DisplayName(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.get(0).isObject() || !args[0].toObject().is<JSFunction>()) {
+ RootedObject arg(cx, &args.callee());
+ ReportUsageErrorASCII(cx, arg, "Must have one function argument");
+ return false;
+ }
+
+ JSFunction* fun = &args[0].toObject().as<JSFunction>();
+ JSString* str = fun->displayAtom();
+ args.rval().setString(str ? str : cx->runtime()->emptyString);
+ return true;
+}
+
+class ShellAllocationMetadataBuilder : public AllocationMetadataBuilder {
+ public:
+ ShellAllocationMetadataBuilder() : AllocationMetadataBuilder() { }
+
+ virtual JSObject* build(JSContext *cx, HandleObject,
+ AutoEnterOOMUnsafeRegion& oomUnsafe) const override;
+
+ static const ShellAllocationMetadataBuilder metadataBuilder;
+};
+
+JSObject*
+ShellAllocationMetadataBuilder::build(JSContext* cx, HandleObject,
+ AutoEnterOOMUnsafeRegion& oomUnsafe) const
+{
+ RootedObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!obj)
+ oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
+
+ RootedObject stack(cx, NewDenseEmptyArray(cx));
+ if (!stack)
+ oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
+
+ static int createdIndex = 0;
+ createdIndex++;
+
+ if (!JS_DefineProperty(cx, obj, "index", createdIndex, 0,
+ JS_STUBGETTER, JS_STUBSETTER))
+ {
+ oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
+ }
+
+ if (!JS_DefineProperty(cx, obj, "stack", stack, 0,
+ JS_STUBGETTER, JS_STUBSETTER))
+ {
+ oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
+ }
+
+ int stackIndex = 0;
+ RootedId id(cx);
+ RootedValue callee(cx);
+ for (NonBuiltinScriptFrameIter iter(cx); !iter.done(); ++iter) {
+ if (iter.isFunctionFrame() && iter.compartment() == cx->compartment()) {
+ id = INT_TO_JSID(stackIndex);
+ RootedObject callee(cx, iter.callee(cx));
+ if (!JS_DefinePropertyById(cx, stack, id, callee, 0,
+ JS_STUBGETTER, JS_STUBSETTER))
+ {
+ oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
+ }
+ stackIndex++;
+ }
+ }
+
+ return obj;
+}
+
+const ShellAllocationMetadataBuilder ShellAllocationMetadataBuilder::metadataBuilder;
+
+static bool
+EnableShellAllocationMetadataBuilder(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ SetAllocationMetadataBuilder(cx, &ShellAllocationMetadataBuilder::metadataBuilder);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+GetAllocationMetadata(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1 || !args[0].isObject()) {
+ JS_ReportErrorASCII(cx, "Argument must be an object");
+ return false;
+ }
+
+ args.rval().setObjectOrNull(GetAllocationMetadata(&args[0].toObject()));
+ return true;
+}
+
+static bool
+testingFunc_bailout(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // NOP when not in IonMonkey
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+testingFunc_bailAfter(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1 || !args[0].isInt32() || args[0].toInt32() < 0) {
+ JS_ReportErrorASCII(cx, "Argument must be a positive number that fits in an int32");
+ return false;
+ }
+
+#ifdef DEBUG
+ cx->runtime()->setIonBailAfter(args[0].toInt32());
+#endif
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+testingFunc_inJit(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!jit::IsBaselineEnabled(cx)) {
+ JSString* error = JS_NewStringCopyZ(cx, "Baseline is disabled.");
+ if(!error)
+ return false;
+
+ args.rval().setString(error);
+ return true;
+ }
+
+ JSScript* script = cx->currentScript();
+ if (script && script->getWarmUpResetCount() >= 20) {
+ JSString* error = JS_NewStringCopyZ(cx, "Compilation is being repeatedly prevented. Giving up.");
+ if (!error)
+ return false;
+
+ args.rval().setString(error);
+ return true;
+ }
+
+ args.rval().setBoolean(cx->currentlyRunningInJit());
+ return true;
+}
+
+static bool
+testingFunc_inIon(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!jit::IsIonEnabled(cx)) {
+ JSString* error = JS_NewStringCopyZ(cx, "Ion is disabled.");
+ if (!error)
+ return false;
+
+ args.rval().setString(error);
+ return true;
+ }
+
+ ScriptFrameIter iter(cx);
+ if (iter.isIon()) {
+ // Reset the counter of the IonScript's script.
+ jit::JitFrameIterator jitIter(cx);
+ ++jitIter;
+ jitIter.script()->resetWarmUpResetCounter();
+ } else {
+ // Check if we missed multiple attempts at compiling the innermost script.
+ JSScript* script = cx->currentScript();
+ if (script && script->getWarmUpResetCount() >= 20) {
+ JSString* error = JS_NewStringCopyZ(cx, "Compilation is being repeatedly prevented. Giving up.");
+ if (!error)
+ return false;
+
+ args.rval().setString(error);
+ return true;
+ }
+ }
+
+ args.rval().setBoolean(iter.isIon());
+ return true;
+}
+
+bool
+js::testingFunc_assertFloat32(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 2) {
+ JS_ReportErrorASCII(cx, "Expects only 2 arguments");
+ return false;
+ }
+
+ // NOP when not in IonMonkey
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+TestingFunc_assertJitStackInvariants(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ jit::AssertJitStackInvariants(cx);
+ args.rval().setUndefined();
+ return true;
+}
+
+bool
+js::testingFunc_assertRecoveredOnBailout(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 2) {
+ JS_ReportErrorASCII(cx, "Expects only 2 arguments");
+ return false;
+ }
+
+ // NOP when not in IonMonkey
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+SetJitCompilerOption(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (args.length() != 2) {
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments.");
+ return false;
+ }
+
+ if (!args[0].isString()) {
+ ReportUsageErrorASCII(cx, callee, "First argument must be a String.");
+ return false;
+ }
+
+ if (!args[1].isInt32()) {
+ ReportUsageErrorASCII(cx, callee, "Second argument must be an Int32.");
+ return false;
+ }
+
+ JSFlatString* strArg = JS_FlattenString(cx, args[0].toString());
+ if (!strArg)
+ return false;
+
+#define JIT_COMPILER_MATCH(key, string) \
+ else if (JS_FlatStringEqualsAscii(strArg, string)) \
+ opt = JSJITCOMPILER_ ## key;
+
+ JSJitCompilerOption opt = JSJITCOMPILER_NOT_AN_OPTION;
+ if (false) {}
+ JIT_COMPILER_OPTIONS(JIT_COMPILER_MATCH);
+#undef JIT_COMPILER_MATCH
+
+ if (opt == JSJITCOMPILER_NOT_AN_OPTION) {
+ ReportUsageErrorASCII(cx, callee, "First argument does not name a valid option (see jsapi.h).");
+ return false;
+ }
+
+ int32_t number = args[1].toInt32();
+ if (number < 0)
+ number = -1;
+
+ // Throw if disabling the JITs and there's JIT code on the stack, to avoid
+ // assertion failures.
+ if ((opt == JSJITCOMPILER_BASELINE_ENABLE || opt == JSJITCOMPILER_ION_ENABLE) &&
+ number == 0)
+ {
+ js::jit::JitActivationIterator iter(cx->runtime());
+ if (!iter.done()) {
+ JS_ReportErrorASCII(cx, "Can't turn off JITs with JIT code on the stack.");
+ return false;
+ }
+ }
+
+ JS_SetGlobalJitCompilerOption(cx, opt, uint32_t(number));
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+GetJitCompilerOptions(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject info(cx, JS_NewPlainObject(cx));
+ if (!info)
+ return false;
+
+ uint32_t intValue = 0;
+ RootedValue value(cx);
+
+#define JIT_COMPILER_MATCH(key, string) \
+ opt = JSJITCOMPILER_ ## key; \
+ if (JS_GetGlobalJitCompilerOption(cx, opt, &intValue)) { \
+ value.setInt32(intValue); \
+ if (!JS_SetProperty(cx, info, string, value)) \
+ return false; \
+ }
+
+ JSJitCompilerOption opt = JSJITCOMPILER_NOT_AN_OPTION;
+ JIT_COMPILER_OPTIONS(JIT_COMPILER_MATCH);
+#undef JIT_COMPILER_MATCH
+
+ args.rval().setObject(*info);
+ return true;
+}
+
+static bool
+SetIonCheckGraphCoherency(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ jit::JitOptions.checkGraphConsistency = ToBoolean(args.get(0));
+ args.rval().setUndefined();
+ return true;
+}
+
+class CloneBufferObject : public NativeObject {
+ static const JSPropertySpec props_[2];
+ static const size_t DATA_SLOT = 0;
+ static const size_t LENGTH_SLOT = 1;
+ static const size_t NUM_SLOTS = 2;
+
+ public:
+ static const Class class_;
+
+ static CloneBufferObject* Create(JSContext* cx) {
+ RootedObject obj(cx, JS_NewObject(cx, Jsvalify(&class_)));
+ if (!obj)
+ return nullptr;
+ obj->as<CloneBufferObject>().setReservedSlot(DATA_SLOT, PrivateValue(nullptr));
+ obj->as<CloneBufferObject>().setReservedSlot(LENGTH_SLOT, Int32Value(0));
+
+ if (!JS_DefineProperties(cx, obj, props_))
+ return nullptr;
+
+ return &obj->as<CloneBufferObject>();
+ }
+
+ static CloneBufferObject* Create(JSContext* cx, JSAutoStructuredCloneBuffer* buffer) {
+ Rooted<CloneBufferObject*> obj(cx, Create(cx));
+ if (!obj)
+ return nullptr;
+ auto data = js::MakeUnique<JSStructuredCloneData>();
+ if (!data) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+ buffer->steal(data.get());
+ obj->setData(data.release());
+ return obj;
+ }
+
+ JSStructuredCloneData* data() const {
+ return static_cast<JSStructuredCloneData*>(getReservedSlot(DATA_SLOT).toPrivate());
+ }
+
+ void setData(JSStructuredCloneData* aData) {
+ MOZ_ASSERT(!data());
+ setReservedSlot(DATA_SLOT, PrivateValue(aData));
+ }
+
+ // Discard an owned clone buffer.
+ void discard() {
+ if (data()) {
+ JSAutoStructuredCloneBuffer clonebuf(JS::StructuredCloneScope::SameProcessSameThread, nullptr, nullptr);
+ clonebuf.adopt(Move(*data()));
+ }
+ setReservedSlot(DATA_SLOT, PrivateValue(nullptr));
+ }
+
+ static bool
+ setCloneBuffer_impl(JSContext* cx, const CallArgs& args) {
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "clonebuffer setter requires a single string argument");
+ return false;
+ }
+ if (!args[0].isString()) {
+ JS_ReportErrorASCII(cx, "clonebuffer value must be a string");
+ return false;
+ }
+
+ if (fuzzingSafe) {
+ // A manually-created clonebuffer could easily trigger a crash
+ args.rval().setUndefined();
+ return true;
+ }
+
+ Rooted<CloneBufferObject*> obj(cx, &args.thisv().toObject().as<CloneBufferObject>());
+ obj->discard();
+
+ char* str = JS_EncodeString(cx, args[0].toString());
+ if (!str)
+ return false;
+ size_t nbytes = JS_GetStringLength(args[0].toString());
+ MOZ_ASSERT(nbytes % sizeof(uint64_t) == 0);
+ auto buf = js::MakeUnique<JSStructuredCloneData>(nbytes, nbytes, nbytes);
+ js_memcpy(buf->Start(), str, nbytes);
+ JS_free(cx, str);
+ obj->setData(buf.release());
+
+ args.rval().setUndefined();
+ return true;
+ }
+
+ static bool
+ is(HandleValue v) {
+ return v.isObject() && v.toObject().is<CloneBufferObject>();
+ }
+
+ static bool
+ setCloneBuffer(JSContext* cx, unsigned int argc, JS::Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<is, setCloneBuffer_impl>(cx, args);
+ }
+
+ static bool
+ getCloneBuffer_impl(JSContext* cx, const CallArgs& args) {
+ Rooted<CloneBufferObject*> obj(cx, &args.thisv().toObject().as<CloneBufferObject>());
+ MOZ_ASSERT(args.length() == 0);
+
+ if (!obj->data()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ bool hasTransferable;
+ if (!JS_StructuredCloneHasTransferables(*obj->data(), &hasTransferable))
+ return false;
+
+ if (hasTransferable) {
+ JS_ReportErrorASCII(cx, "cannot retrieve structured clone buffer with transferables");
+ return false;
+ }
+
+ size_t size = obj->data()->Size();
+ UniqueChars buffer(static_cast<char*>(js_malloc(size)));
+ if (!buffer) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ auto iter = obj->data()->Iter();
+ obj->data()->ReadBytes(iter, buffer.get(), size);
+ JSString* str = JS_NewStringCopyN(cx, buffer.get(), size);
+ if (!str)
+ return false;
+ args.rval().setString(str);
+ return true;
+ }
+
+ static bool
+ getCloneBuffer(JSContext* cx, unsigned int argc, JS::Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<is, getCloneBuffer_impl>(cx, args);
+ }
+
+ static void Finalize(FreeOp* fop, JSObject* obj) {
+ obj->as<CloneBufferObject>().discard();
+ }
+};
+
+static const ClassOps CloneBufferObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ CloneBufferObject::Finalize
+};
+
+const Class CloneBufferObject::class_ = {
+ "CloneBuffer",
+ JSCLASS_HAS_RESERVED_SLOTS(CloneBufferObject::NUM_SLOTS) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &CloneBufferObjectClassOps
+};
+
+const JSPropertySpec CloneBufferObject::props_[] = {
+ JS_PSGS("clonebuffer", getCloneBuffer, setCloneBuffer, 0),
+ JS_PS_END
+};
+
+static mozilla::Maybe<JS::StructuredCloneScope>
+ParseCloneScope(JSContext* cx, HandleString str)
+{
+ mozilla::Maybe<JS::StructuredCloneScope> scope;
+
+ JSAutoByteString scopeStr(cx, str);
+ if (!scopeStr)
+ return scope;
+
+ if (strcmp(scopeStr.ptr(), "SameProcessSameThread") == 0)
+ scope.emplace(JS::StructuredCloneScope::SameProcessSameThread);
+ else if (strcmp(scopeStr.ptr(), "SameProcessDifferentThread") == 0)
+ scope.emplace(JS::StructuredCloneScope::SameProcessDifferentThread);
+ else if (strcmp(scopeStr.ptr(), "DifferentProcess") == 0)
+ scope.emplace(JS::StructuredCloneScope::DifferentProcess);
+
+ return scope;
+}
+
+static bool
+Serialize(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ mozilla::Maybe<JSAutoStructuredCloneBuffer> clonebuf;
+ JS::CloneDataPolicy policy;
+
+ if (!args.get(2).isUndefined()) {
+ RootedObject opts(cx, ToObject(cx, args.get(2)));
+ if (!opts)
+ return false;
+
+ RootedValue v(cx);
+ if (!JS_GetProperty(cx, opts, "SharedArrayBuffer", &v))
+ return false;
+
+ if (!v.isUndefined()) {
+ JSString* str = JS::ToString(cx, v);
+ if (!str)
+ return false;
+ JSAutoByteString poli(cx, str);
+ if (!poli)
+ return false;
+
+ if (strcmp(poli.ptr(), "allow") == 0) {
+ // default
+ } else if (strcmp(poli.ptr(), "deny") == 0) {
+ policy.denySharedArrayBuffer();
+ } else {
+ JS_ReportErrorASCII(cx, "Invalid policy value for 'SharedArrayBuffer'");
+ return false;
+ }
+ }
+
+ if (!JS_GetProperty(cx, opts, "scope", &v))
+ return false;
+
+ if (!v.isUndefined()) {
+ RootedString str(cx, JS::ToString(cx, v));
+ if (!str)
+ return false;
+ auto scope = ParseCloneScope(cx, str);
+ if (!scope) {
+ JS_ReportErrorASCII(cx, "Invalid structured clone scope");
+ return false;
+ }
+ clonebuf.emplace(*scope, nullptr, nullptr);
+ }
+ }
+
+ if (!clonebuf)
+ clonebuf.emplace(JS::StructuredCloneScope::SameProcessSameThread, nullptr, nullptr);
+
+ if (!clonebuf->write(cx, args.get(0), args.get(1), policy))
+ return false;
+
+ RootedObject obj(cx, CloneBufferObject::Create(cx, clonebuf.ptr()));
+ if (!obj)
+ return false;
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool
+Deserialize(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.get(0).isObject() || !args[0].toObject().is<CloneBufferObject>()) {
+ JS_ReportErrorASCII(cx, "deserialize requires a clonebuffer argument");
+ return false;
+ }
+
+ JS::StructuredCloneScope scope = JS::StructuredCloneScope::SameProcessSameThread;
+ if (args.get(1).isObject()) {
+ RootedObject opts(cx, &args[1].toObject());
+ if (!opts)
+ return false;
+
+ RootedValue v(cx);
+ if (!JS_GetProperty(cx, opts, "scope", &v))
+ return false;
+
+ if (!v.isUndefined()) {
+ RootedString str(cx, JS::ToString(cx, v));
+ if (!str)
+ return false;
+ auto maybeScope = ParseCloneScope(cx, str);
+ if (!maybeScope) {
+ JS_ReportErrorASCII(cx, "Invalid structured clone scope");
+ return false;
+ }
+
+ scope = *maybeScope;
+ }
+ }
+
+ Rooted<CloneBufferObject*> obj(cx, &args[0].toObject().as<CloneBufferObject>());
+
+ // Clone buffer was already consumed?
+ if (!obj->data()) {
+ JS_ReportErrorASCII(cx, "deserialize given invalid clone buffer "
+ "(transferables already consumed?)");
+ return false;
+ }
+
+ bool hasTransferable;
+ if (!JS_StructuredCloneHasTransferables(*obj->data(), &hasTransferable))
+ return false;
+
+ RootedValue deserialized(cx);
+ if (!JS_ReadStructuredClone(cx, *obj->data(),
+ JS_STRUCTURED_CLONE_VERSION,
+ scope,
+ &deserialized, nullptr, nullptr))
+ {
+ return false;
+ }
+ args.rval().set(deserialized);
+
+ if (hasTransferable)
+ obj->discard();
+
+ return true;
+}
+
+static bool
+DetachArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "detachArrayBuffer() requires a single argument");
+ return false;
+ }
+
+ if (!args[0].isObject()) {
+ JS_ReportErrorASCII(cx, "detachArrayBuffer must be passed an object");
+ return false;
+ }
+
+ RootedObject obj(cx, &args[0].toObject());
+ if (!JS_DetachArrayBuffer(cx, obj))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+HelperThreadCount(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+#ifdef JS_MORE_DETERMINISTIC
+ // Always return 0 to get consistent output with and without --no-threads.
+ args.rval().setInt32(0);
+#else
+ if (CanUseExtraThreads())
+ args.rval().setInt32(HelperThreadState().threadCount);
+ else
+ args.rval().setInt32(0);
+#endif
+ return true;
+}
+
+static bool
+TimesAccessed(JSContext* cx, unsigned argc, Value* vp)
+{
+ static int32_t accessed = 0;
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setInt32(++accessed);
+ return true;
+}
+
+#ifdef JS_TRACE_LOGGING
+static bool
+EnableTraceLogger(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
+ if (!TraceLoggerEnable(logger, cx))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+DisableTraceLogger(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
+ args.rval().setBoolean(TraceLoggerDisable(logger));
+
+ return true;
+}
+#endif
+
+#ifdef DEBUG
+static bool
+DumpObject(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject obj(cx, ToObject(cx, args.get(0)));
+ if (!obj)
+ return false;
+
+ DumpObject(obj);
+
+ args.rval().setUndefined();
+ return true;
+}
+#endif
+
+static bool
+SharedMemoryEnabled(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(cx->compartment()->creationOptions().getSharedMemoryAndAtomicsEnabled());
+ return true;
+}
+
+#ifdef NIGHTLY_BUILD
+static bool
+ObjectAddress(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+ if (!args[0].isObject()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Expected object");
+ return false;
+ }
+
+#ifdef JS_MORE_DETERMINISTIC
+ args.rval().setInt32(0);
+#else
+ void* ptr = js::UncheckedUnwrap(&args[0].toObject(), true);
+ char buffer[64];
+ SprintfLiteral(buffer, "%p", ptr);
+
+ JSString* str = JS_NewStringCopyZ(cx, buffer);
+ if (!str)
+ return false;
+
+ args.rval().setString(str);
+#endif
+
+ return true;
+}
+
+static bool
+SharedAddress(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+ if (!args[0].isObject()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Expected object");
+ return false;
+ }
+
+#ifdef JS_MORE_DETERMINISTIC
+ args.rval().setString(cx->staticStrings().getUint(0));
+#else
+ RootedObject obj(cx, CheckedUnwrap(&args[0].toObject()));
+ if (!obj) {
+ JS_ReportErrorASCII(cx, "Permission denied to access object");
+ return false;
+ }
+ if (!obj->is<SharedArrayBufferObject>()) {
+ JS_ReportErrorASCII(cx, "Argument must be a SharedArrayBuffer");
+ return false;
+ }
+ char buffer[64];
+ uint32_t nchar =
+ SprintfLiteral(buffer, "%p",
+ obj->as<SharedArrayBufferObject>().dataPointerShared().unwrap(/*safeish*/));
+
+ JSString* str = JS_NewStringCopyN(cx, buffer, nchar);
+ if (!str)
+ return false;
+
+ args.rval().setString(str);
+#endif
+
+ return true;
+}
+#endif
+
+static bool
+DumpBacktrace(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ DumpBacktrace(cx);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+GetBacktrace(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ bool showArgs = false;
+ bool showLocals = false;
+ bool showThisProps = false;
+
+ if (args.length() > 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Too many arguments");
+ return false;
+ }
+
+ if (args.length() == 1) {
+ RootedObject cfg(cx, ToObject(cx, args[0]));
+ if (!cfg)
+ return false;
+ RootedValue v(cx);
+
+ if (!JS_GetProperty(cx, cfg, "args", &v))
+ return false;
+ showArgs = ToBoolean(v);
+
+ if (!JS_GetProperty(cx, cfg, "locals", &v))
+ return false;
+ showLocals = ToBoolean(v);
+
+ if (!JS_GetProperty(cx, cfg, "thisprops", &v))
+ return false;
+ showThisProps = ToBoolean(v);
+ }
+
+ char* buf = JS::FormatStackDump(cx, nullptr, showArgs, showLocals, showThisProps);
+ if (!buf)
+ return false;
+
+ RootedString str(cx);
+ if (!(str = JS_NewStringCopyZ(cx, buf)))
+ return false;
+ JS_smprintf_free(buf);
+
+ args.rval().setString(str);
+ return true;
+}
+
+static bool
+ReportOutOfMemory(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ JS_ReportOutOfMemory(cx);
+ cx->clearPendingException();
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ThrowOutOfMemory(JSContext* cx, unsigned argc, Value* vp)
+{
+ JS_ReportOutOfMemory(cx);
+ return false;
+}
+
+static bool
+ReportLargeAllocationFailure(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ void* buf = cx->runtime()->onOutOfMemoryCanGC(AllocFunction::Malloc, JSRuntime::LARGE_ALLOCATION);
+ js_free(buf);
+ args.rval().setUndefined();
+ return true;
+}
+
+namespace heaptools {
+
+typedef UniqueTwoByteChars EdgeName;
+
+// An edge to a node from its predecessor in a path through the graph.
+class BackEdge {
+ // The node from which this edge starts.
+ JS::ubi::Node predecessor_;
+
+ // The name of this edge.
+ EdgeName name_;
+
+ public:
+ BackEdge() : name_(nullptr) { }
+ // Construct an initialized back edge, taking ownership of |name|.
+ BackEdge(JS::ubi::Node predecessor, EdgeName name)
+ : predecessor_(predecessor), name_(Move(name)) { }
+ BackEdge(BackEdge&& rhs) : predecessor_(rhs.predecessor_), name_(Move(rhs.name_)) { }
+ BackEdge& operator=(BackEdge&& rhs) {
+ MOZ_ASSERT(&rhs != this);
+ this->~BackEdge();
+ new(this) BackEdge(Move(rhs));
+ return *this;
+ }
+
+ EdgeName forgetName() { return Move(name_); }
+ JS::ubi::Node predecessor() const { return predecessor_; }
+
+ private:
+ // No copy constructor or copying assignment.
+ BackEdge(const BackEdge&) = delete;
+ BackEdge& operator=(const BackEdge&) = delete;
+};
+
+// A path-finding handler class for use with JS::ubi::BreadthFirst.
+struct FindPathHandler {
+ typedef BackEdge NodeData;
+ typedef JS::ubi::BreadthFirst<FindPathHandler> Traversal;
+
+ FindPathHandler(JSContext*cx, JS::ubi::Node start, JS::ubi::Node target,
+ MutableHandle<GCVector<Value>> nodes, Vector<EdgeName>& edges)
+ : cx(cx), start(start), target(target), foundPath(false),
+ nodes(nodes), edges(edges) { }
+
+ bool
+ operator()(Traversal& traversal, JS::ubi::Node origin, const JS::ubi::Edge& edge,
+ BackEdge* backEdge, bool first)
+ {
+ // We take care of each node the first time we visit it, so there's
+ // nothing to be done on subsequent visits.
+ if (!first)
+ return true;
+
+ // Record how we reached this node. This is the last edge on a
+ // shortest path to this node.
+ EdgeName edgeName = DuplicateString(cx, edge.name.get());
+ if (!edgeName)
+ return false;
+ *backEdge = mozilla::Move(BackEdge(origin, Move(edgeName)));
+
+ // Have we reached our final target node?
+ if (edge.referent == target) {
+ // Record the path that got us here, which must be a shortest path.
+ if (!recordPath(traversal))
+ return false;
+ foundPath = true;
+ traversal.stop();
+ }
+
+ return true;
+ }
+
+ // We've found a path to our target. Walk the backlinks to produce the
+ // (reversed) path, saving the path in |nodes| and |edges|. |nodes| is
+ // rooted, so it can hold the path's nodes as we leave the scope of
+ // the AutoCheckCannotGC.
+ bool recordPath(Traversal& traversal) {
+ JS::ubi::Node here = target;
+
+ do {
+ Traversal::NodeMap::Ptr p = traversal.visited.lookup(here);
+ MOZ_ASSERT(p);
+ JS::ubi::Node predecessor = p->value().predecessor();
+ if (!nodes.append(predecessor.exposeToJS()) ||
+ !edges.append(p->value().forgetName()))
+ return false;
+ here = predecessor;
+ } while (here != start);
+
+ return true;
+ }
+
+ JSContext* cx;
+
+ // The node we're starting from.
+ JS::ubi::Node start;
+
+ // The node we're looking for.
+ JS::ubi::Node target;
+
+ // True if we found a path to target, false if we didn't.
+ bool foundPath;
+
+ // The nodes and edges of the path --- should we find one. The path is
+ // stored in reverse order, because that's how it's easiest for us to
+ // construct it:
+ // - edges[i] is the name of the edge from nodes[i] to nodes[i-1].
+ // - edges[0] is the name of the edge from nodes[0] to the target.
+ // - The last node, nodes[n-1], is the start node.
+ MutableHandle<GCVector<Value>> nodes;
+ Vector<EdgeName>& edges;
+};
+
+} // namespace heaptools
+
+static bool
+FindPath(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (argc < 2) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED,
+ "findPath", "1", "");
+ return false;
+ }
+
+ // We don't ToString non-objects given as 'start' or 'target', because this
+ // test is all about object identity, and ToString doesn't preserve that.
+ // Non-GCThing endpoints don't make much sense.
+ if (!args[0].isObject() && !args[0].isString() && !args[0].isSymbol()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[0], nullptr,
+ "not an object, string, or symbol", NULL);
+ return false;
+ }
+
+ if (!args[1].isObject() && !args[1].isString() && !args[1].isSymbol()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[0], nullptr,
+ "not an object, string, or symbol", NULL);
+ return false;
+ }
+
+ Rooted<GCVector<Value>> nodes(cx, GCVector<Value>(cx));
+ Vector<heaptools::EdgeName> edges(cx);
+
+ {
+ // We can't tolerate the GC moving things around while we're searching
+ // the heap. Check that nothing we do causes a GC.
+ JS::AutoCheckCannotGC autoCannotGC;
+
+ JS::ubi::Node start(args[0]), target(args[1]);
+
+ heaptools::FindPathHandler handler(cx, start, target, &nodes, edges);
+ heaptools::FindPathHandler::Traversal traversal(cx, handler, autoCannotGC);
+ if (!traversal.init() || !traversal.addStart(start)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ if (!traversal.traverse()) {
+ if (!cx->isExceptionPending())
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ if (!handler.foundPath) {
+ // We didn't find any paths from the start to the target.
+ args.rval().setUndefined();
+ return true;
+ }
+ }
+
+ // |nodes| and |edges| contain the path from |start| to |target|, reversed.
+ // Construct a JavaScript array describing the path from the start to the
+ // target. Each element has the form:
+ //
+ // {
+ // node: <object or string or symbol>,
+ // edge: <string describing outgoing edge from node>
+ // }
+ //
+ // or, if the node is some internal thing that isn't a proper JavaScript
+ // value:
+ //
+ // { node: undefined, edge: <string> }
+ size_t length = nodes.length();
+ RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, length));
+ if (!result)
+ return false;
+ result->ensureDenseInitializedLength(cx, 0, length);
+
+ // Walk |nodes| and |edges| in the stored order, and construct the result
+ // array in start-to-target order.
+ for (size_t i = 0; i < length; i++) {
+ // Build an object describing the node and edge.
+ RootedObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!obj)
+ return false;
+
+ RootedValue wrapped(cx, nodes[i]);
+ if (!cx->compartment()->wrap(cx, &wrapped))
+ return false;
+
+ if (!JS_DefineProperty(cx, obj, "node", wrapped,
+ JSPROP_ENUMERATE, nullptr, nullptr))
+ return false;
+
+ heaptools::EdgeName edgeName = Move(edges[i]);
+
+ RootedString edgeStr(cx, NewString<CanGC>(cx, edgeName.get(), js_strlen(edgeName.get())));
+ if (!edgeStr)
+ return false;
+ mozilla::Unused << edgeName.release(); // edgeStr acquired ownership
+
+ if (!JS_DefineProperty(cx, obj, "edge", edgeStr, JSPROP_ENUMERATE, nullptr, nullptr))
+ return false;
+
+ result->setDenseElement(length - i - 1, ObjectValue(*obj));
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+static bool
+ShortestPaths(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "shortestPaths", 3))
+ return false;
+
+ // We don't ToString non-objects given as 'start' or 'target', because this
+ // test is all about object identity, and ToString doesn't preserve that.
+ // Non-GCThing endpoints don't make much sense.
+ if (!args[0].isObject() && !args[0].isString() && !args[0].isSymbol()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[0], nullptr,
+ "not an object, string, or symbol", nullptr);
+ return false;
+ }
+
+ if (!args[1].isObject() || !args[1].toObject().is<ArrayObject>()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[1], nullptr,
+ "not an array object", nullptr);
+ return false;
+ }
+
+ RootedArrayObject objs(cx, &args[1].toObject().as<ArrayObject>());
+ size_t length = objs->getDenseInitializedLength();
+ if (length == 0) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[1], nullptr,
+ "not a dense array object with one or more elements", nullptr);
+ return false;
+ }
+
+ for (size_t i = 0; i < length; i++) {
+ RootedValue el(cx, objs->getDenseElement(i));
+ if (!el.isObject() && !el.isString() && !el.isSymbol()) {
+ JS_ReportErrorASCII(cx, "Each target must be an object, string, or symbol");
+ return false;
+ }
+ }
+
+ int32_t maxNumPaths;
+ if (!JS::ToInt32(cx, args[2], &maxNumPaths))
+ return false;
+ if (maxNumPaths <= 0) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[2], nullptr,
+ "not greater than 0", nullptr);
+ return false;
+ }
+
+ // We accumulate the results into a GC-stable form, due to the fact that the
+ // JS::ubi::ShortestPaths lifetime (when operating on the live heap graph)
+ // is bounded within an AutoCheckCannotGC.
+ Rooted<GCVector<GCVector<GCVector<Value>>>> values(cx, GCVector<GCVector<GCVector<Value>>>(cx));
+ Vector<Vector<Vector<JS::ubi::EdgeName>>> names(cx);
+
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+
+ JS::ubi::NodeSet targets;
+ if (!targets.init()) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ for (size_t i = 0; i < length; i++) {
+ RootedValue val(cx, objs->getDenseElement(i));
+ JS::ubi::Node node(val);
+ if (!targets.put(node)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ JS::ubi::Node root(args[0]);
+ auto maybeShortestPaths = JS::ubi::ShortestPaths::Create(cx, noGC, maxNumPaths,
+ root, mozilla::Move(targets));
+ if (maybeShortestPaths.isNothing()) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ auto& shortestPaths = *maybeShortestPaths;
+
+ for (size_t i = 0; i < length; i++) {
+ if (!values.append(GCVector<GCVector<Value>>(cx)) ||
+ !names.append(Vector<Vector<JS::ubi::EdgeName>>(cx)))
+ {
+ return false;
+ }
+
+ RootedValue val(cx, objs->getDenseElement(i));
+ JS::ubi::Node target(val);
+
+ bool ok = shortestPaths.forEachPath(target, [&](JS::ubi::Path& path) {
+ Rooted<GCVector<Value>> pathVals(cx, GCVector<Value>(cx));
+ Vector<JS::ubi::EdgeName> pathNames(cx);
+
+ for (auto& part : path) {
+ if (!pathVals.append(part->predecessor().exposeToJS()) ||
+ !pathNames.append(mozilla::Move(part->name())))
+ {
+ return false;
+ }
+ }
+
+ return values.back().append(mozilla::Move(pathVals.get())) &&
+ names.back().append(mozilla::Move(pathNames));
+ });
+
+ if (!ok)
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(values.length() == names.length());
+ MOZ_ASSERT(values.length() == length);
+
+ RootedArrayObject results(cx, NewDenseFullyAllocatedArray(cx, length));
+ if (!results)
+ return false;
+ results->ensureDenseInitializedLength(cx, 0, length);
+
+ for (size_t i = 0; i < length; i++) {
+ size_t numPaths = values[i].length();
+ MOZ_ASSERT(names[i].length() == numPaths);
+
+ RootedArrayObject pathsArray(cx, NewDenseFullyAllocatedArray(cx, numPaths));
+ if (!pathsArray)
+ return false;
+ pathsArray->ensureDenseInitializedLength(cx, 0, numPaths);
+
+ for (size_t j = 0; j < numPaths; j++) {
+ size_t pathLength = values[i][j].length();
+ MOZ_ASSERT(names[i][j].length() == pathLength);
+
+ RootedArrayObject path(cx, NewDenseFullyAllocatedArray(cx, pathLength));
+ if (!path)
+ return false;
+ path->ensureDenseInitializedLength(cx, 0, pathLength);
+
+ for (size_t k = 0; k < pathLength; k++) {
+ RootedPlainObject part(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!part)
+ return false;
+
+ RootedValue predecessor(cx, values[i][j][k]);
+ if (!cx->compartment()->wrap(cx, &predecessor) ||
+ !JS_DefineProperty(cx, part, "predecessor", predecessor, JSPROP_ENUMERATE))
+ {
+ return false;
+ }
+
+ if (names[i][j][k]) {
+ RootedString edge(cx, NewStringCopyZ<CanGC>(cx, names[i][j][k].get()));
+ if (!edge || !JS_DefineProperty(cx, part, "edge", edge, JSPROP_ENUMERATE))
+ return false;
+ }
+
+ path->setDenseElement(k, ObjectValue(*part));
+ }
+
+ pathsArray->setDenseElement(j, ObjectValue(*path));
+ }
+
+ results->setDenseElement(i, ObjectValue(*pathsArray));
+ }
+
+ args.rval().setObject(*results);
+ return true;
+}
+
+static bool
+EvalReturningScope(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "evalReturningScope", 1))
+ return false;
+
+ RootedString str(cx, ToString(cx, args[0]));
+ if (!str)
+ return false;
+
+ RootedObject global(cx);
+ if (args.hasDefined(1)) {
+ global = ToObject(cx, args[1]);
+ if (!global)
+ return false;
+ }
+
+ AutoStableStringChars strChars(cx);
+ if (!strChars.initTwoByte(cx, str))
+ return false;
+
+ mozilla::Range<const char16_t> chars = strChars.twoByteRange();
+ size_t srclen = chars.length();
+ const char16_t* src = chars.begin().get();
+
+ JS::AutoFilename filename;
+ unsigned lineno;
+
+ JS::DescribeScriptedCaller(cx, &filename, &lineno);
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(filename.get(), lineno);
+ options.setNoScriptRval(true);
+
+ JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership);
+ RootedScript script(cx);
+ if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script))
+ return false;
+
+ if (global) {
+ global = CheckedUnwrap(global);
+ if (!global) {
+ JS_ReportErrorASCII(cx, "Permission denied to access global");
+ return false;
+ }
+ if (!global->is<GlobalObject>()) {
+ JS_ReportErrorASCII(cx, "Argument must be a global object");
+ return false;
+ }
+ } else {
+ global = JS::CurrentGlobalOrNull(cx);
+ }
+
+ RootedObject varObj(cx);
+ RootedObject lexicalScope(cx);
+
+ {
+ // If we're switching globals here, ExecuteInGlobalAndReturnScope will
+ // take care of cloning the script into that compartment before
+ // executing it.
+ AutoCompartment ac(cx, global);
+
+ if (!js::ExecuteInGlobalAndReturnScope(cx, global, script, &lexicalScope))
+ return false;
+
+ varObj = lexicalScope->enclosingEnvironment();
+ }
+
+ RootedObject rv(cx, JS_NewPlainObject(cx));
+ if (!rv)
+ return false;
+
+ RootedValue varObjVal(cx, ObjectValue(*varObj));
+ if (!cx->compartment()->wrap(cx, &varObjVal))
+ return false;
+ if (!JS_SetProperty(cx, rv, "vars", varObjVal))
+ return false;
+
+ RootedValue lexicalScopeVal(cx, ObjectValue(*lexicalScope));
+ if (!cx->compartment()->wrap(cx, &lexicalScopeVal))
+ return false;
+ if (!JS_SetProperty(cx, rv, "lexicals", lexicalScopeVal))
+ return false;
+
+ args.rval().setObject(*rv);
+ return true;
+}
+
+static bool
+ShellCloneAndExecuteScript(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "cloneAndExecuteScript", 2))
+ return false;
+
+ RootedString str(cx, ToString(cx, args[0]));
+ if (!str)
+ return false;
+
+ RootedObject global(cx, ToObject(cx, args[1]));
+ if (!global)
+ return false;
+
+ AutoStableStringChars strChars(cx);
+ if (!strChars.initTwoByte(cx, str))
+ return false;
+
+ mozilla::Range<const char16_t> chars = strChars.twoByteRange();
+ size_t srclen = chars.length();
+ const char16_t* src = chars.begin().get();
+
+ JS::AutoFilename filename;
+ unsigned lineno;
+
+ JS::DescribeScriptedCaller(cx, &filename, &lineno);
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(filename.get(), lineno);
+ options.setNoScriptRval(true);
+
+ JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership);
+ RootedScript script(cx);
+ if (!JS::Compile(cx, options, srcBuf, &script))
+ return false;
+
+ global = CheckedUnwrap(global);
+ if (!global) {
+ JS_ReportErrorASCII(cx, "Permission denied to access global");
+ return false;
+ }
+ if (!global->is<GlobalObject>()) {
+ JS_ReportErrorASCII(cx, "Argument must be a global object");
+ return false;
+ }
+
+ AutoCompartment ac(cx, global);
+
+ JS::RootedValue rval(cx);
+ if (!JS::CloneAndExecuteScript(cx, script, &rval))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+IsSimdAvailable(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+#if defined(JS_CODEGEN_NONE) || !defined(ENABLE_SIMD)
+ bool available = false;
+#else
+ bool available = cx->jitSupportsSimd();
+#endif
+ args.rval().set(BooleanValue(available));
+ return true;
+}
+
+static bool
+ByteSize(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ mozilla::MallocSizeOf mallocSizeOf = cx->runtime()->debuggerMallocSizeOf;
+
+ {
+ // We can't tolerate the GC moving things around while we're using a
+ // ubi::Node. Check that nothing we do causes a GC.
+ JS::AutoCheckCannotGC autoCannotGC;
+
+ JS::ubi::Node node = args.get(0);
+ if (node)
+ args.rval().setNumber(uint32_t(node.size(mallocSizeOf)));
+ else
+ args.rval().setUndefined();
+ }
+ return true;
+}
+
+static bool
+ByteSizeOfScript(JSContext*cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "byteSizeOfScript", 1))
+ return false;
+ if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+ JS_ReportErrorASCII(cx, "Argument must be a Function object");
+ return false;
+ }
+
+ JSFunction* fun = &args[0].toObject().as<JSFunction>();
+ if (fun->isNative()) {
+ JS_ReportErrorASCII(cx, "Argument must be a scripted function");
+ return false;
+ }
+
+ RootedScript script(cx, fun->getOrCreateScript(cx));
+ if (!script)
+ return false;
+
+ mozilla::MallocSizeOf mallocSizeOf = cx->runtime()->debuggerMallocSizeOf;
+
+ {
+ // We can't tolerate the GC moving things around while we're using a
+ // ubi::Node. Check that nothing we do causes a GC.
+ JS::AutoCheckCannotGC autoCannotGC;
+
+ JS::ubi::Node node = script;
+ if (node)
+ args.rval().setNumber(uint32_t(node.size(mallocSizeOf)));
+ else
+ args.rval().setUndefined();
+ }
+ return true;
+}
+
+static bool
+SetImmutablePrototype(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.get(0).isObject()) {
+ JS_ReportErrorASCII(cx, "setImmutablePrototype: object expected");
+ return false;
+ }
+
+ RootedObject obj(cx, &args[0].toObject());
+
+ bool succeeded;
+ if (!js::SetImmutablePrototype(cx, obj, &succeeded))
+ return false;
+
+ args.rval().setBoolean(succeeded);
+ return true;
+}
+
+#ifdef DEBUG
+static bool
+DumpStringRepresentation(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedString str(cx, ToString(cx, args.get(0)));
+ if (!str)
+ return false;
+
+ str->dumpRepresentation(stderr, 0);
+
+ args.rval().setUndefined();
+ return true;
+}
+#endif
+
+static bool
+SetLazyParsingDisabled(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ bool disable = !args.hasDefined(0) || ToBoolean(args[0]);
+ cx->compartment()->behaviors().setDisableLazyParsing(disable);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+SetDiscardSource(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ bool discard = !args.hasDefined(0) || ToBoolean(args[0]);
+ cx->compartment()->behaviors().setDiscardSource(discard);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+GetConstructorName(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "getConstructorName", 1))
+ return false;
+
+ if (!args[0].isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
+ "getConstructorName", "Object",
+ InformalValueTypeName(args[0]));
+ return false;
+ }
+
+ RootedAtom name(cx);
+ if (!args[0].toObject().constructorDisplayAtom(cx, &name))
+ return false;
+
+ if (name) {
+ args.rval().setString(name);
+ } else {
+ args.rval().setNull();
+ }
+ return true;
+}
+
+static bool
+AllocationMarker(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ bool allocateInsideNursery = true;
+ if (args.length() > 0 && args[0].isObject()) {
+ RootedObject options(cx, &args[0].toObject());
+
+ RootedValue nurseryVal(cx);
+ if (!JS_GetProperty(cx, options, "nursery", &nurseryVal))
+ return false;
+ allocateInsideNursery = ToBoolean(nurseryVal);
+ }
+
+ static const Class cls = { "AllocationMarker" };
+
+ auto newKind = allocateInsideNursery ? GenericObject : TenuredObject;
+ RootedObject obj(cx, NewObjectWithGivenProto(cx, &cls, nullptr, newKind));
+ if (!obj)
+ return false;
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+namespace gcCallback {
+
+struct MajorGC {
+ int32_t depth;
+ int32_t phases;
+};
+
+static void
+majorGC(JSContext* cx, JSGCStatus status, void* data)
+{
+ auto info = static_cast<MajorGC*>(data);
+ if (!(info->phases & (1 << status)))
+ return;
+
+ if (info->depth > 0) {
+ info->depth--;
+ JS::PrepareForFullGC(cx);
+ JS::GCForReason(cx, GC_NORMAL, JS::gcreason::API);
+ info->depth++;
+ }
+}
+
+struct MinorGC {
+ int32_t phases;
+ bool active;
+};
+
+static void
+minorGC(JSContext* cx, JSGCStatus status, void* data)
+{
+ auto info = static_cast<MinorGC*>(data);
+ if (!(info->phases & (1 << status)))
+ return;
+
+ if (info->active) {
+ info->active = false;
+ cx->gc.evictNursery(JS::gcreason::DEBUG_GC);
+ info->active = true;
+ }
+}
+
+// Process global, should really be runtime-local. Also, the final one of these
+// is currently leaked, since they are only deleted when changing.
+MajorGC* prevMajorGC = nullptr;
+MinorGC* prevMinorGC = nullptr;
+
+} /* namespace gcCallback */
+
+static bool
+SetGCCallback(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "Wrong number of arguments");
+ return false;
+ }
+
+ RootedObject opts(cx, ToObject(cx, args[0]));
+ if (!opts)
+ return false;
+
+ RootedValue v(cx);
+ if (!JS_GetProperty(cx, opts, "action", &v))
+ return false;
+
+ JSString* str = JS::ToString(cx, v);
+ if (!str)
+ return false;
+ JSAutoByteString action(cx, str);
+ if (!action)
+ return false;
+
+ int32_t phases = 0;
+ if ((strcmp(action.ptr(), "minorGC") == 0) || (strcmp(action.ptr(), "majorGC") == 0)) {
+ if (!JS_GetProperty(cx, opts, "phases", &v))
+ return false;
+ if (v.isUndefined()) {
+ phases = (1 << JSGC_END);
+ } else {
+ JSString* str = JS::ToString(cx, v);
+ if (!str)
+ return false;
+ JSAutoByteString phasesStr(cx, str);
+ if (!phasesStr)
+ return false;
+
+ if (strcmp(phasesStr.ptr(), "begin") == 0)
+ phases = (1 << JSGC_BEGIN);
+ else if (strcmp(phasesStr.ptr(), "end") == 0)
+ phases = (1 << JSGC_END);
+ else if (strcmp(phasesStr.ptr(), "both") == 0)
+ phases = (1 << JSGC_BEGIN) | (1 << JSGC_END);
+ else {
+ JS_ReportErrorASCII(cx, "Invalid callback phase");
+ return false;
+ }
+ }
+ }
+
+ if (gcCallback::prevMajorGC) {
+ JS_SetGCCallback(cx, nullptr, nullptr);
+ js_delete<gcCallback::MajorGC>(gcCallback::prevMajorGC);
+ gcCallback::prevMajorGC = nullptr;
+ }
+
+ if (gcCallback::prevMinorGC) {
+ JS_SetGCCallback(cx, nullptr, nullptr);
+ js_delete<gcCallback::MinorGC>(gcCallback::prevMinorGC);
+ gcCallback::prevMinorGC = nullptr;
+ }
+
+ if (strcmp(action.ptr(), "minorGC") == 0) {
+ auto info = js_new<gcCallback::MinorGC>();
+ if (!info) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ info->phases = phases;
+ info->active = true;
+ JS_SetGCCallback(cx, gcCallback::minorGC, info);
+ } else if (strcmp(action.ptr(), "majorGC") == 0) {
+ if (!JS_GetProperty(cx, opts, "depth", &v))
+ return false;
+ int32_t depth = 1;
+ if (!v.isUndefined()) {
+ if (!ToInt32(cx, v, &depth))
+ return false;
+ }
+ if (depth > int32_t(gcstats::Statistics::MAX_NESTING - 4)) {
+ JS_ReportErrorASCII(cx, "Nesting depth too large, would overflow");
+ return false;
+ }
+
+ auto info = js_new<gcCallback::MajorGC>();
+ if (!info) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ info->phases = phases;
+ info->depth = depth;
+ JS_SetGCCallback(cx, gcCallback::majorGC, info);
+ } else {
+ JS_ReportErrorASCII(cx, "Unknown GC callback action");
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+GetLcovInfo(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() > 1) {
+ JS_ReportErrorASCII(cx, "Wrong number of arguments");
+ return false;
+ }
+
+ RootedObject global(cx);
+ if (args.hasDefined(0)) {
+ global = ToObject(cx, args[0]);
+ if (!global) {
+ JS_ReportErrorASCII(cx, "First argument should be an object");
+ return false;
+ }
+ global = CheckedUnwrap(global);
+ if (!global) {
+ JS_ReportErrorASCII(cx, "Permission denied to access global");
+ return false;
+ }
+ if (!global->is<GlobalObject>()) {
+ JS_ReportErrorASCII(cx, "Argument must be a global object");
+ return false;
+ }
+ } else {
+ global = JS::CurrentGlobalOrNull(cx);
+ }
+
+ size_t length = 0;
+ char* content = nullptr;
+ {
+ AutoCompartment ac(cx, global);
+ content = js::GetCodeCoverageSummary(cx, &length);
+ }
+
+ if (!content)
+ return false;
+
+ JSString* str = JS_NewStringCopyN(cx, content, length);
+ free(content);
+
+ if (!str)
+ return false;
+
+ args.rval().setString(str);
+ return true;
+}
+
+#ifdef DEBUG
+static bool
+SetRNGState(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "SetRNGState", 2))
+ return false;
+
+ double d0;
+ if (!ToNumber(cx, args[0], &d0))
+ return false;
+
+ double d1;
+ if (!ToNumber(cx, args[1], &d1))
+ return false;
+
+ uint64_t seed0 = static_cast<uint64_t>(d0);
+ uint64_t seed1 = static_cast<uint64_t>(d1);
+
+ if (seed0 == 0 && seed1 == 0) {
+ JS_ReportErrorASCII(cx, "RNG requires non-zero seed");
+ return false;
+ }
+
+ cx->compartment()->ensureRandomNumberGenerator();
+ cx->compartment()->randomNumberGenerator.ref().setState(seed0, seed1);
+
+ args.rval().setUndefined();
+ return true;
+}
+#endif
+
+static ModuleEnvironmentObject*
+GetModuleEnvironment(JSContext* cx, HandleValue moduleValue)
+{
+ RootedModuleObject module(cx, &moduleValue.toObject().as<ModuleObject>());
+
+ // Use the initial environment so that tests can check bindings exists
+ // before they have been instantiated.
+ RootedModuleEnvironmentObject env(cx, &module->initialEnvironment());
+ MOZ_ASSERT(env);
+ MOZ_ASSERT_IF(module->environment(), module->environment() == env);
+
+ return env;
+}
+
+static bool
+GetModuleEnvironmentNames(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "Wrong number of arguments");
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) {
+ JS_ReportErrorASCII(cx, "First argument should be a ModuleObject");
+ return false;
+ }
+
+ RootedModuleEnvironmentObject env(cx, GetModuleEnvironment(cx, args[0]));
+ Rooted<IdVector> ids(cx, IdVector(cx));
+ if (!JS_Enumerate(cx, env, &ids))
+ return false;
+
+ uint32_t length = ids.length();
+ RootedArrayObject array(cx, NewDenseFullyAllocatedArray(cx, length));
+ if (!array)
+ return false;
+
+ array->setDenseInitializedLength(length);
+ for (uint32_t i = 0; i < length; i++)
+ array->initDenseElement(i, StringValue(JSID_TO_STRING(ids[i])));
+
+ args.rval().setObject(*array);
+ return true;
+}
+
+static bool
+GetModuleEnvironmentValue(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 2) {
+ JS_ReportErrorASCII(cx, "Wrong number of arguments");
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) {
+ JS_ReportErrorASCII(cx, "First argument should be a ModuleObject");
+ return false;
+ }
+
+ if (!args[1].isString()) {
+ JS_ReportErrorASCII(cx, "Second argument should be a string");
+ return false;
+ }
+
+ RootedModuleEnvironmentObject env(cx, GetModuleEnvironment(cx, args[0]));
+ RootedString name(cx, args[1].toString());
+ RootedId id(cx);
+ if (!JS_StringToId(cx, name, &id))
+ return false;
+
+ return GetProperty(cx, env, env, id, args.rval());
+}
+
+#ifdef DEBUG
+static const char*
+AssertionTypeToString(irregexp::RegExpAssertion::AssertionType type)
+{
+ switch (type) {
+ case irregexp::RegExpAssertion::START_OF_LINE:
+ return "START_OF_LINE";
+ case irregexp::RegExpAssertion::START_OF_INPUT:
+ return "START_OF_INPUT";
+ case irregexp::RegExpAssertion::END_OF_LINE:
+ return "END_OF_LINE";
+ case irregexp::RegExpAssertion::END_OF_INPUT:
+ return "END_OF_INPUT";
+ case irregexp::RegExpAssertion::BOUNDARY:
+ return "BOUNDARY";
+ case irregexp::RegExpAssertion::NON_BOUNDARY:
+ return "NON_BOUNDARY";
+ case irregexp::RegExpAssertion::NOT_AFTER_LEAD_SURROGATE:
+ return "NOT_AFTER_LEAD_SURROGATE";
+ case irregexp::RegExpAssertion::NOT_IN_SURROGATE_PAIR:
+ return "NOT_IN_SURROGATE_PAIR";
+ }
+ MOZ_CRASH("unexpected AssertionType");
+}
+
+static JSObject*
+ConvertRegExpTreeToObject(JSContext* cx, irregexp::RegExpTree* tree)
+{
+ RootedObject obj(cx, JS_NewPlainObject(cx));
+ if (!obj)
+ return nullptr;
+
+ auto IntProp = [](JSContext* cx, HandleObject obj,
+ const char* name, int32_t value) {
+ RootedValue val(cx, Int32Value(value));
+ return JS_SetProperty(cx, obj, name, val);
+ };
+
+ auto BooleanProp = [](JSContext* cx, HandleObject obj,
+ const char* name, bool value) {
+ RootedValue val(cx, BooleanValue(value));
+ return JS_SetProperty(cx, obj, name, val);
+ };
+
+ auto StringProp = [](JSContext* cx, HandleObject obj,
+ const char* name, const char* value) {
+ RootedString valueStr(cx, JS_NewStringCopyZ(cx, value));
+ if (!valueStr)
+ return false;
+
+ RootedValue val(cx, StringValue(valueStr));
+ return JS_SetProperty(cx, obj, name, val);
+ };
+
+ auto ObjectProp = [](JSContext* cx, HandleObject obj,
+ const char* name, HandleObject value) {
+ RootedValue val(cx, ObjectValue(*value));
+ return JS_SetProperty(cx, obj, name, val);
+ };
+
+ auto CharVectorProp = [](JSContext* cx, HandleObject obj,
+ const char* name, const irregexp::CharacterVector& data) {
+ RootedString valueStr(cx, JS_NewUCStringCopyN(cx, data.begin(), data.length()));
+ if (!valueStr)
+ return false;
+
+ RootedValue val(cx, StringValue(valueStr));
+ return JS_SetProperty(cx, obj, name, val);
+ };
+
+ auto TreeProp = [&ObjectProp](JSContext* cx, HandleObject obj,
+ const char* name, irregexp::RegExpTree* tree) {
+ RootedObject treeObj(cx, ConvertRegExpTreeToObject(cx, tree));
+ if (!treeObj)
+ return false;
+ return ObjectProp(cx, obj, name, treeObj);
+ };
+
+ auto TreeVectorProp = [&ObjectProp](JSContext* cx, HandleObject obj,
+ const char* name,
+ const irregexp::RegExpTreeVector& nodes) {
+ size_t len = nodes.length();
+ RootedObject array(cx, JS_NewArrayObject(cx, len));
+ if (!array)
+ return false;
+
+ for (size_t i = 0; i < len; i++) {
+ RootedObject child(cx, ConvertRegExpTreeToObject(cx, nodes[i]));
+ if (!child)
+ return false;
+
+ RootedValue childVal(cx, ObjectValue(*child));
+ if (!JS_SetElement(cx, array, i, childVal))
+ return false;
+ }
+ return ObjectProp(cx, obj, name, array);
+ };
+
+ auto CharRangesProp = [&ObjectProp](JSContext* cx, HandleObject obj,
+ const char* name,
+ const irregexp::CharacterRangeVector& ranges) {
+ size_t len = ranges.length();
+ RootedObject array(cx, JS_NewArrayObject(cx, len));
+ if (!array)
+ return false;
+
+ for (size_t i = 0; i < len; i++) {
+ const irregexp::CharacterRange& range = ranges[i];
+ RootedObject rangeObj(cx, JS_NewPlainObject(cx));
+ if (!rangeObj)
+ return false;
+
+ auto CharProp = [](JSContext* cx, HandleObject obj,
+ const char* name, char16_t c) {
+ RootedString valueStr(cx, JS_NewUCStringCopyN(cx, &c, 1));
+ if (!valueStr)
+ return false;
+ RootedValue val(cx, StringValue(valueStr));
+ return JS_SetProperty(cx, obj, name, val);
+ };
+
+ if (!CharProp(cx, rangeObj, "from", range.from()))
+ return false;
+ if (!CharProp(cx, rangeObj, "to", range.to()))
+ return false;
+
+ RootedValue rangeVal(cx, ObjectValue(*rangeObj));
+ if (!JS_SetElement(cx, array, i, rangeVal))
+ return false;
+ }
+ return ObjectProp(cx, obj, name, array);
+ };
+
+ auto ElemProp = [&ObjectProp](JSContext* cx, HandleObject obj,
+ const char* name, const irregexp::TextElementVector& elements) {
+ size_t len = elements.length();
+ RootedObject array(cx, JS_NewArrayObject(cx, len));
+ if (!array)
+ return false;
+
+ for (size_t i = 0; i < len; i++) {
+ const irregexp::TextElement& element = elements[i];
+ RootedObject elemTree(cx, ConvertRegExpTreeToObject(cx, element.tree()));
+ if (!elemTree)
+ return false;
+
+ RootedValue elemTreeVal(cx, ObjectValue(*elemTree));
+ if (!JS_SetElement(cx, array, i, elemTreeVal))
+ return false;
+ }
+ return ObjectProp(cx, obj, name, array);
+ };
+
+ if (tree->IsDisjunction()) {
+ if (!StringProp(cx, obj, "type", "Disjunction"))
+ return nullptr;
+ irregexp::RegExpDisjunction* t = tree->AsDisjunction();
+ if (!TreeVectorProp(cx, obj, "alternatives", t->alternatives()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsAlternative()) {
+ if (!StringProp(cx, obj, "type", "Alternative"))
+ return nullptr;
+ irregexp::RegExpAlternative* t = tree->AsAlternative();
+ if (!TreeVectorProp(cx, obj, "nodes", t->nodes()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsAssertion()) {
+ if (!StringProp(cx, obj, "type", "Assertion"))
+ return nullptr;
+ irregexp::RegExpAssertion* t = tree->AsAssertion();
+ if (!StringProp(cx, obj, "assertion_type", AssertionTypeToString(t->assertion_type())))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsCharacterClass()) {
+ if (!StringProp(cx, obj, "type", "CharacterClass"))
+ return nullptr;
+ irregexp::RegExpCharacterClass* t = tree->AsCharacterClass();
+ if (!BooleanProp(cx, obj, "is_negated", t->is_negated()))
+ return nullptr;
+ LifoAlloc* alloc = &cx->tempLifoAlloc();
+ if (!CharRangesProp(cx, obj, "ranges", t->ranges(alloc)))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsAtom()) {
+ if (!StringProp(cx, obj, "type", "Atom"))
+ return nullptr;
+ irregexp::RegExpAtom* t = tree->AsAtom();
+ if (!CharVectorProp(cx, obj, "data", t->data()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsText()) {
+ if (!StringProp(cx, obj, "type", "Text"))
+ return nullptr;
+ irregexp::RegExpText* t = tree->AsText();
+ if (!ElemProp(cx, obj, "elements", t->elements()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsQuantifier()) {
+ if (!StringProp(cx, obj, "type", "Quantifier"))
+ return nullptr;
+ irregexp::RegExpQuantifier* t = tree->AsQuantifier();
+ if (!IntProp(cx, obj, "min", t->min()))
+ return nullptr;
+ if (!IntProp(cx, obj, "max", t->max()))
+ return nullptr;
+ if (!StringProp(cx, obj, "quantifier_type",
+ t->is_possessive() ? "POSSESSIVE"
+ : t->is_non_greedy() ? "NON_GREEDY"
+ : "GREEDY"))
+ return nullptr;
+ if (!TreeProp(cx, obj, "body", t->body()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsCapture()) {
+ if (!StringProp(cx, obj, "type", "Capture"))
+ return nullptr;
+ irregexp::RegExpCapture* t = tree->AsCapture();
+ if (!IntProp(cx, obj, "index", t->index()))
+ return nullptr;
+ if (!TreeProp(cx, obj, "body", t->body()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsLookahead()) {
+ if (!StringProp(cx, obj, "type", "Lookahead"))
+ return nullptr;
+ irregexp::RegExpLookahead* t = tree->AsLookahead();
+ if (!BooleanProp(cx, obj, "is_positive", t->is_positive()))
+ return nullptr;
+ if (!TreeProp(cx, obj, "body", t->body()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsBackReference()) {
+ if (!StringProp(cx, obj, "type", "BackReference"))
+ return nullptr;
+ irregexp::RegExpBackReference* t = tree->AsBackReference();
+ if (!IntProp(cx, obj, "index", t->index()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsEmpty()) {
+ if (!StringProp(cx, obj, "type", "Empty"))
+ return nullptr;
+ return obj;
+ }
+
+ MOZ_CRASH("unexpected RegExpTree type");
+}
+
+static bool
+ParseRegExp(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (args.length() == 0) {
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ if (!args[0].isString()) {
+ ReportUsageErrorASCII(cx, callee, "First argument must be a String");
+ return false;
+ }
+
+ RegExpFlag flags = RegExpFlag(0);
+ if (!args.get(1).isUndefined()) {
+ if (!args.get(1).isString()) {
+ ReportUsageErrorASCII(cx, callee, "Second argument, if present, must be a String");
+ return false;
+ }
+ RootedString flagStr(cx, args[1].toString());
+ if (!ParseRegExpFlags(cx, flagStr, &flags))
+ return false;
+ }
+
+ bool match_only = false;
+ if (!args.get(2).isUndefined()) {
+ if (!args.get(2).isBoolean()) {
+ ReportUsageErrorASCII(cx, callee, "Third argument, if present, must be a Boolean");
+ return false;
+ }
+ match_only = args[2].toBoolean();
+ }
+
+ RootedAtom pattern(cx, AtomizeString(cx, args[0].toString()));
+ if (!pattern)
+ return false;
+
+ CompileOptions options(cx);
+ frontend::TokenStream dummyTokenStream(cx, options, nullptr, 0, nullptr);
+
+ irregexp::RegExpCompileData data;
+ if (!irregexp::ParsePattern(dummyTokenStream, cx->tempLifoAlloc(), pattern,
+ flags & MultilineFlag, match_only,
+ flags & UnicodeFlag, flags & IgnoreCaseFlag,
+ flags & GlobalFlag, flags & StickyFlag,
+ &data))
+ {
+ return false;
+ }
+
+ RootedObject obj(cx, ConvertRegExpTreeToObject(cx, data.tree));
+ if (!obj)
+ return false;
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool
+DisRegExp(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (args.length() == 0) {
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<RegExpObject>()) {
+ ReportUsageErrorASCII(cx, callee, "First argument must be a RegExp");
+ return false;
+ }
+
+ Rooted<RegExpObject*> reobj(cx, &args[0].toObject().as<RegExpObject>());
+
+ bool match_only = false;
+ if (!args.get(1).isUndefined()) {
+ if (!args.get(1).isBoolean()) {
+ ReportUsageErrorASCII(cx, callee, "Second argument, if present, must be a Boolean");
+ return false;
+ }
+ match_only = args[1].toBoolean();
+ }
+
+ RootedLinearString input(cx, cx->runtime()->emptyString);
+ if (!args.get(2).isUndefined()) {
+ if (!args.get(2).isString()) {
+ ReportUsageErrorASCII(cx, callee, "Third argument, if present, must be a String");
+ return false;
+ }
+ RootedString inputStr(cx, args[2].toString());
+ input = inputStr->ensureLinear(cx);
+ if (!input)
+ return false;
+ }
+
+ if (!reobj->dumpBytecode(cx, match_only, input))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+#endif // DEBUG
+
+static bool
+IsConstructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() < 1)
+ args.rval().setBoolean(false);
+ else
+ args.rval().setBoolean(IsConstructor(args[0]));
+ return true;
+}
+
+static const JSFunctionSpecWithHelp TestingFunctions[] = {
+ JS_FN_HELP("gc", ::GC, 0, 0,
+"gc([obj] | 'zone' [, 'shrinking'])",
+" Run the garbage collector. When obj is given, GC only its zone.\n"
+" If 'zone' is given, GC any zones that were scheduled for\n"
+" GC via schedulegc.\n"
+" If 'shrinking' is passed as the optional second argument, perform a\n"
+" shrinking GC rather than a normal GC."),
+
+ JS_FN_HELP("minorgc", ::MinorGC, 0, 0,
+"minorgc([aboutToOverflow])",
+" Run a minor collector on the Nursery. When aboutToOverflow is true, marks\n"
+" the store buffer as about-to-overflow before collecting."),
+
+ JS_FN_HELP("gcparam", GCParameter, 2, 0,
+"gcparam(name [, value])",
+" Wrapper for JS_[GS]etGCParameter. The name is one of:" GC_PARAMETER_ARGS_LIST),
+
+ JS_FN_HELP("relazifyFunctions", RelazifyFunctions, 0, 0,
+"relazifyFunctions(...)",
+" Perform a GC and allow relazification of functions. Accepts the same\n"
+" arguments as gc()."),
+
+ JS_FN_HELP("getBuildConfiguration", GetBuildConfiguration, 0, 0,
+"getBuildConfiguration()",
+" Return an object describing some of the configuration options SpiderMonkey\n"
+" was built with."),
+
+ JS_FN_HELP("hasChild", HasChild, 0, 0,
+"hasChild(parent, child)",
+" Return true if |child| is a child of |parent|, as determined by a call to\n"
+" TraceChildren"),
+
+ JS_FN_HELP("setSavedStacksRNGState", SetSavedStacksRNGState, 1, 0,
+"setSavedStacksRNGState(seed)",
+" Set this compartment's SavedStacks' RNG state.\n"),
+
+ JS_FN_HELP("getSavedFrameCount", GetSavedFrameCount, 0, 0,
+"getSavedFrameCount()",
+" Return the number of SavedFrame instances stored in this compartment's\n"
+" SavedStacks cache."),
+
+ JS_FN_HELP("saveStack", SaveStack, 0, 0,
+"saveStack([maxDepth [, compartment]])",
+" Capture a stack. If 'maxDepth' is given, capture at most 'maxDepth' number\n"
+" of frames. If 'compartment' is given, allocate the js::SavedFrame instances\n"
+" with the given object's compartment."),
+
+ JS_FN_HELP("captureFirstSubsumedFrame", CaptureFirstSubsumedFrame, 1, 0,
+"saveStack(object [, shouldIgnoreSelfHosted = true]])",
+" Capture a stack back to the first frame whose principals are subsumed by the\n"
+" object's compartment's principals. If 'shouldIgnoreSelfHosted' is given,\n"
+" control whether self-hosted frames are considered when checking principals."),
+
+ JS_FN_HELP("callFunctionFromNativeFrame", CallFunctionFromNativeFrame, 1, 0,
+"callFunctionFromNativeFrame(function)",
+" Call 'function' with a (C++-)native frame on stack.\n"
+" Required for testing that SaveStack properly handles native frames."),
+
+ JS_FN_HELP("callFunctionWithAsyncStack", CallFunctionWithAsyncStack, 0, 0,
+"callFunctionWithAsyncStack(function, stack, asyncCause)",
+" Call 'function', using the provided stack as the async stack responsible\n"
+" for the call, and propagate its return value or the exception it throws.\n"
+" The function is called with no arguments, and 'this' is 'undefined'. The\n"
+" specified |asyncCause| is attached to the provided stack frame."),
+
+ JS_FN_HELP("enableTrackAllocations", EnableTrackAllocations, 0, 0,
+"enableTrackAllocations()",
+" Start capturing the JS stack at every allocation. Note that this sets an\n"
+" object metadata callback that will override any other object metadata\n"
+" callback that may be set."),
+
+ JS_FN_HELP("disableTrackAllocations", DisableTrackAllocations, 0, 0,
+"disableTrackAllocations()",
+" Stop capturing the JS stack at every allocation."),
+
+ JS_FN_HELP("newExternalString", NewExternalString, 1, 0,
+"newExternalString(str)",
+" Copies str's chars and returns a new external string."),
+
+ JS_FN_HELP("ensureFlatString", EnsureFlatString, 1, 0,
+"ensureFlatString(str)",
+" Ensures str is a flat (null-terminated) string and returns it."),
+
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+ JS_FN_HELP("oomThreadTypes", OOMThreadTypes, 0, 0,
+"oomThreadTypes()",
+" Get the number of thread types that can be used as an argument for\n"
+"oomAfterAllocations() and oomAtAllocation()."),
+
+ JS_FN_HELP("oomAfterAllocations", OOMAfterAllocations, 2, 0,
+"oomAfterAllocations(count [,threadType])",
+" After 'count' js_malloc memory allocations, fail every following allocation\n"
+" (return nullptr). The optional thread type limits the effect to the\n"
+" specified type of helper thread."),
+
+ JS_FN_HELP("oomAtAllocation", OOMAtAllocation, 2, 0,
+"oomAtAllocation(count [,threadType])",
+" After 'count' js_malloc memory allocations, fail the next allocation\n"
+" (return nullptr). The optional thread type limits the effect to the\n"
+" specified type of helper thread."),
+
+ JS_FN_HELP("resetOOMFailure", ResetOOMFailure, 0, 0,
+"resetOOMFailure()",
+" Remove the allocation failure scheduled by either oomAfterAllocations() or\n"
+" oomAtAllocation() and return whether any allocation had been caused to fail."),
+
+ JS_FN_HELP("oomTest", OOMTest, 0, 0,
+"oomTest(function, [expectExceptionOnFailure = true])",
+" Test that the passed function behaves correctly under OOM conditions by\n"
+" repeatedly executing it and simulating allocation failure at successive\n"
+" allocations until the function completes without seeing a failure.\n"
+" By default this tests that an exception is raised if execution fails, but\n"
+" this can be disabled by passing false as the optional second parameter.\n"
+" This is also disabled when --fuzzing-safe is specified."),
+#endif
+
+#ifdef SPIDERMONKEY_PROMISE
+ JS_FN_HELP("settlePromiseNow", SettlePromiseNow, 1, 0,
+"settlePromiseNow(promise)",
+" 'Settle' a 'promise' immediately. This just marks the promise as resolved\n"
+" with a value of `undefined` and causes the firing of any onPromiseSettled\n"
+" hooks set on Debugger instances that are observing the given promise's\n"
+" global as a debuggee."),
+ JS_FN_HELP("getWaitForAllPromise", GetWaitForAllPromise, 1, 0,
+"getWaitForAllPromise(densePromisesArray)",
+" Calls the 'GetWaitForAllPromise' JSAPI function and returns the result\n"
+" Promise."),
+JS_FN_HELP("resolvePromise", ResolvePromise, 2, 0,
+"resolvePromise(promise, resolution)",
+" Resolve a Promise by calling the JSAPI function JS::ResolvePromise."),
+JS_FN_HELP("rejectPromise", RejectPromise, 2, 0,
+"rejectPromise(promise, reason)",
+" Reject a Promise by calling the JSAPI function JS::RejectPromise."),
+#else
+ JS_FN_HELP("makeFakePromise", MakeFakePromise, 0, 0,
+"makeFakePromise()",
+" Create an object whose [[Class]] name is 'Promise' and call\n"
+" JS::dbg::onNewPromise on it before returning it. It doesn't actually have\n"
+" any of the other behavior associated with promises."),
+
+ JS_FN_HELP("settleFakePromise", SettleFakePromise, 1, 0,
+"settleFakePromise(promise)",
+" 'Settle' a 'promise' created by makeFakePromise(). This doesn't have any\n"
+" observable effects outside of firing any onPromiseSettled hooks set on\n"
+" Debugger instances that are observing the given promise's global as a\n"
+" debuggee."),
+#endif // SPIDERMONKEY_PROMISE
+
+ JS_FN_HELP("makeFinalizeObserver", MakeFinalizeObserver, 0, 0,
+"makeFinalizeObserver()",
+" Get a special object whose finalization increases the counter returned\n"
+" by the finalizeCount function."),
+
+ JS_FN_HELP("finalizeCount", FinalizeCount, 0, 0,
+"finalizeCount()",
+" Return the current value of the finalization counter that is incremented\n"
+" each time an object returned by the makeFinalizeObserver is finalized."),
+
+ JS_FN_HELP("resetFinalizeCount", ResetFinalizeCount, 0, 0,
+"resetFinalizeCount()",
+" Reset the value returned by finalizeCount()."),
+
+ JS_FN_HELP("gcPreserveCode", GCPreserveCode, 0, 0,
+"gcPreserveCode()",
+" Preserve JIT code during garbage collections."),
+
+#ifdef JS_GC_ZEAL
+ JS_FN_HELP("gczeal", GCZeal, 2, 0,
+"gczeal(level, [N])",
+gc::ZealModeHelpText),
+
+ JS_FN_HELP("schedulegc", ScheduleGC, 1, 0,
+"schedulegc([num | obj | string])",
+" If num is given, schedule a GC after num allocations.\n"
+" If obj is given, schedule a GC of obj's zone.\n"
+" If string is given, schedule a GC of the string's zone if possible.\n"
+" Returns the number of allocations before the next trigger."),
+
+ JS_FN_HELP("selectforgc", SelectForGC, 0, 0,
+"selectforgc(obj1, obj2, ...)",
+" Schedule the given objects to be marked in the next GC slice."),
+
+ JS_FN_HELP("verifyprebarriers", VerifyPreBarriers, 0, 0,
+"verifyprebarriers()",
+" Start or end a run of the pre-write barrier verifier."),
+
+ JS_FN_HELP("verifypostbarriers", VerifyPostBarriers, 0, 0,
+"verifypostbarriers()",
+" Does nothing (the post-write barrier verifier has been remove)."),
+
+ JS_FN_HELP("gcstate", GCState, 0, 0,
+"gcstate()",
+" Report the global GC state."),
+
+ JS_FN_HELP("deterministicgc", DeterministicGC, 1, 0,
+"deterministicgc(true|false)",
+" If true, only allow determinstic GCs to run."),
+#endif
+
+ JS_FN_HELP("startgc", StartGC, 1, 0,
+"startgc([n [, 'shrinking']])",
+" Start an incremental GC and run a slice that processes about n objects.\n"
+" If 'shrinking' is passesd as the optional second argument, perform a\n"
+" shrinking GC rather than a normal GC."),
+
+ JS_FN_HELP("gcslice", GCSlice, 1, 0,
+"gcslice([n])",
+" Start or continue an an incremental GC, running a slice that processes about n objects."),
+
+ JS_FN_HELP("abortgc", AbortGC, 1, 0,
+"abortgc()",
+" Abort the current incremental GC."),
+
+ JS_FN_HELP("fullcompartmentchecks", FullCompartmentChecks, 1, 0,
+"fullcompartmentchecks(true|false)",
+" If true, check for compartment mismatches before every GC."),
+
+ JS_FN_HELP("nondeterministicGetWeakMapKeys", NondeterministicGetWeakMapKeys, 1, 0,
+"nondeterministicGetWeakMapKeys(weakmap)",
+" Return an array of the keys in the given WeakMap."),
+
+ JS_FN_HELP("internalConst", InternalConst, 1, 0,
+"internalConst(name)",
+" Query an internal constant for the engine. See InternalConst source for\n"
+" the list of constant names."),
+
+ JS_FN_HELP("isProxy", IsProxy, 1, 0,
+"isProxy(obj)",
+" If true, obj is a proxy of some sort"),
+
+ JS_FN_HELP("dumpHeap", DumpHeap, 1, 0,
+"dumpHeap(['collectNurseryBeforeDump'], [filename])",
+" Dump reachable and unreachable objects to the named file, or to stdout. If\n"
+" 'collectNurseryBeforeDump' is specified, a minor GC is performed first,\n"
+" otherwise objects in the nursery are ignored."),
+
+ JS_FN_HELP("terminate", Terminate, 0, 0,
+"terminate()",
+" Terminate JavaScript execution, as if we had run out of\n"
+" memory or been terminated by the slow script dialog."),
+
+ JS_FN_HELP("readSPSProfilingStack", ReadSPSProfilingStack, 0, 0,
+"readSPSProfilingStack()",
+" Reads the jit stack using ProfilingFrameIterator."),
+
+ JS_FN_HELP("enableOsiPointRegisterChecks", EnableOsiPointRegisterChecks, 0, 0,
+"enableOsiPointRegisterChecks()",
+"Emit extra code to verify live regs at the start of a VM call are not\n"
+"modified before its OsiPoint."),
+
+ JS_FN_HELP("displayName", DisplayName, 1, 0,
+"displayName(fn)",
+" Gets the display name for a function, which can possibly be a guessed or\n"
+" inferred name based on where the function was defined. This can be\n"
+" different from the 'name' property on the function."),
+
+ JS_FN_HELP("isAsmJSCompilationAvailable", IsAsmJSCompilationAvailable, 0, 0,
+"isAsmJSCompilationAvailable",
+" Returns whether asm.js compilation is currently available or whether it is disabled\n"
+" (e.g., by the debugger)."),
+
+ JS_FN_HELP("isSimdAvailable", IsSimdAvailable, 0, 0,
+"isSimdAvailable",
+" Returns true if SIMD extensions are supported on this platform."),
+
+ JS_FN_HELP("getJitCompilerOptions", GetJitCompilerOptions, 0, 0,
+"getCompilerOptions()",
+"Return an object describing some of the JIT compiler options.\n"),
+
+ JS_FN_HELP("isAsmJSModule", IsAsmJSModule, 1, 0,
+"isAsmJSModule(fn)",
+" Returns whether the given value is a function containing \"use asm\" that has been\n"
+" validated according to the asm.js spec."),
+
+ JS_FN_HELP("isAsmJSModuleLoadedFromCache", IsAsmJSModuleLoadedFromCache, 1, 0,
+"isAsmJSModuleLoadedFromCache(fn)",
+" Return whether the given asm.js module function has been loaded directly\n"
+" from the cache. This function throws an error if fn is not a validated asm.js\n"
+" module."),
+
+ JS_FN_HELP("isAsmJSFunction", IsAsmJSFunction, 1, 0,
+"isAsmJSFunction(fn)",
+" Returns whether the given value is a nested function in an asm.js module that has been\n"
+" both compile- and link-time validated."),
+
+ JS_FN_HELP("wasmIsSupported", WasmIsSupported, 0, 0,
+"wasmIsSupported()",
+" Returns a boolean indicating whether WebAssembly is supported on the current device."),
+
+ JS_FN_HELP("wasmTextToBinary", WasmTextToBinary, 1, 0,
+"wasmTextToBinary(str)",
+" Translates the given text wasm module into its binary encoding."),
+
+ JS_FN_HELP("wasmBinaryToText", WasmBinaryToText, 1, 0,
+"wasmBinaryToText(bin)",
+" Translates binary encoding to text format"),
+
+ JS_FN_HELP("wasmExtractCode", WasmExtractCode, 1, 0,
+"wasmExtractCode(module)",
+" Extracts generated machine code from WebAssembly.Module."),
+
+ JS_FN_HELP("isLazyFunction", IsLazyFunction, 1, 0,
+"isLazyFunction(fun)",
+" True if fun is a lazy JSFunction."),
+
+ JS_FN_HELP("isRelazifiableFunction", IsRelazifiableFunction, 1, 0,
+"isRelazifiableFunction(fun)",
+" Ture if fun is a JSFunction with a relazifiable JSScript."),
+
+ JS_FN_HELP("enableShellAllocationMetadataBuilder", EnableShellAllocationMetadataBuilder, 0, 0,
+"enableShellAllocationMetadataBuilder()",
+" Use ShellAllocationMetadataBuilder to supply metadata for all newly created objects."),
+
+ JS_FN_HELP("getAllocationMetadata", GetAllocationMetadata, 1, 0,
+"getAllocationMetadata(obj)",
+" Get the metadata for an object."),
+
+ JS_INLINABLE_FN_HELP("bailout", testingFunc_bailout, 0, 0, TestBailout,
+"bailout()",
+" Force a bailout out of ionmonkey (if running in ionmonkey)."),
+
+ JS_FN_HELP("bailAfter", testingFunc_bailAfter, 1, 0,
+"bailAfter(number)",
+" Start a counter to bail once after passing the given amount of possible bailout positions in\n"
+" ionmonkey.\n"),
+
+
+ JS_FN_HELP("inJit", testingFunc_inJit, 0, 0,
+"inJit()",
+" Returns true when called within (jit-)compiled code. When jit compilation is disabled this\n"
+" function returns an error string. This function returns false in all other cases.\n"
+" Depending on truthiness, you should continue to wait for compilation to happen or stop execution.\n"),
+
+ JS_FN_HELP("inIon", testingFunc_inIon, 0, 0,
+"inIon()",
+" Returns true when called within ion. When ion is disabled or when compilation is abnormally\n"
+" slow to start, this function returns an error string. Otherwise, this function returns false.\n"
+" This behaviour ensures that a falsy value means that we are not in ion, but expect a\n"
+" compilation to occur in the future. Conversely, a truthy value means that we are either in\n"
+" ion or that there is litle or no chance of ion ever compiling the current script."),
+
+ JS_FN_HELP("assertJitStackInvariants", TestingFunc_assertJitStackInvariants, 0, 0,
+"assertJitStackInvariants()",
+" Iterates the Jit stack and check that stack invariants hold."),
+
+ JS_FN_HELP("setJitCompilerOption", SetJitCompilerOption, 2, 0,
+"setCompilerOption(<option>, <number>)",
+" Set a compiler option indexed in JSCompileOption enum to a number.\n"),
+
+ JS_FN_HELP("setIonCheckGraphCoherency", SetIonCheckGraphCoherency, 1, 0,
+"setIonCheckGraphCoherency(bool)",
+" Set whether Ion should perform graph consistency (DEBUG-only) assertions. These assertions\n"
+" are valuable and should be generally enabled, however they can be very expensive for large\n"
+" (wasm) programs."),
+
+ JS_FN_HELP("serialize", Serialize, 1, 0,
+"serialize(data, [transferables, [policy]])",
+" Serialize 'data' using JS_WriteStructuredClone. Returns a structured\n"
+" clone buffer object. 'policy' may be an options hash. Valid keys:\n"
+" 'SharedArrayBuffer' - either 'allow' (the default) or 'deny'\n"
+" to specify whether SharedArrayBuffers may be serialized.\n"
+"\n"
+" 'scope' - SameProcessSameThread, SameProcessDifferentThread, or\n"
+" DifferentProcess. Determines how some values will be serialized.\n"
+" Clone buffers may only be deserialized with a compatible scope."),
+
+ JS_FN_HELP("deserialize", Deserialize, 1, 0,
+"deserialize(clonebuffer[, opts])",
+" Deserialize data generated by serialize. 'opts' is an options hash with one\n"
+" recognized key 'scope', which limits the clone buffers that are considered\n"
+" valid. Allowed values: 'SameProcessSameThread', 'SameProcessDifferentThread',\n"
+" and 'DifferentProcess'. So for example, a DifferentProcess clone buffer\n"
+" may be deserialized in any scope, but a SameProcessSameThread clone buffer\n"
+" cannot be deserialized in a DifferentProcess scope."),
+
+ JS_FN_HELP("detachArrayBuffer", DetachArrayBuffer, 1, 0,
+"detachArrayBuffer(buffer)",
+" Detach the given ArrayBuffer object from its memory, i.e. as if it\n"
+" had been transferred to a WebWorker."),
+
+ JS_FN_HELP("helperThreadCount", HelperThreadCount, 0, 0,
+"helperThreadCount()",
+" Returns the number of helper threads available for off-main-thread tasks."),
+
+#ifdef JS_TRACE_LOGGING
+ JS_FN_HELP("startTraceLogger", EnableTraceLogger, 0, 0,
+"startTraceLogger()",
+" Start logging the mainThread.\n"
+" Note: tracelogging starts automatically. Disable it by setting environment variable\n"
+" TLOPTIONS=disableMainThread"),
+
+ JS_FN_HELP("stopTraceLogger", DisableTraceLogger, 0, 0,
+"stopTraceLogger()",
+" Stop logging the mainThread."),
+#endif
+
+ JS_FN_HELP("reportOutOfMemory", ReportOutOfMemory, 0, 0,
+"reportOutOfMemory()",
+" Report OOM, then clear the exception and return undefined. For crash testing."),
+
+ JS_FN_HELP("throwOutOfMemory", ThrowOutOfMemory, 0, 0,
+"throwOutOfMemory()",
+" Throw out of memory exception, for OOM handling testing."),
+
+ JS_FN_HELP("reportLargeAllocationFailure", ReportLargeAllocationFailure, 0, 0,
+"reportLargeAllocationFailure()",
+" Call the large allocation failure callback, as though a large malloc call failed,\n"
+" then return undefined. In Gecko, this sends a memory pressure notification, which\n"
+" can free up some memory."),
+
+ JS_FN_HELP("findPath", FindPath, 2, 0,
+"findPath(start, target)",
+" Return an array describing one of the shortest paths of GC heap edges from\n"
+" |start| to |target|, or |undefined| if |target| is unreachable from |start|.\n"
+" Each element of the array is either of the form:\n"
+" { node: <object or string>, edge: <string describing edge from node> }\n"
+" if the node is a JavaScript object or value; or of the form:\n"
+" { type: <string describing node>, edge: <string describing edge> }\n"
+" if the node is some internal thing that is not a proper JavaScript value\n"
+" (like a shape or a scope chain element). The destination of the i'th array\n"
+" element's edge is the node of the i+1'th array element; the destination of\n"
+" the last array element is implicitly |target|.\n"),
+
+ JS_FN_HELP("shortestPaths", ShortestPaths, 3, 0,
+"shortestPaths(start, targets, maxNumPaths)",
+" Return an array of arrays of shortest retaining paths. There is an array of\n"
+" shortest retaining paths for each object in |targets|. The maximum number of\n"
+" paths in each of those arrays is bounded by |maxNumPaths|. Each element in a\n"
+" path is of the form |{ predecessor, edge }|."),
+
+#ifdef DEBUG
+ JS_FN_HELP("dumpObject", DumpObject, 1, 0,
+"dumpObject()",
+" Dump an internal representation of an object."),
+#endif
+
+ JS_FN_HELP("sharedMemoryEnabled", SharedMemoryEnabled, 0, 0,
+"sharedMemoryEnabled()",
+" Return true if SharedArrayBuffer and Atomics are enabled"),
+
+#ifdef NIGHTLY_BUILD
+ JS_FN_HELP("objectAddress", ObjectAddress, 1, 0,
+"objectAddress(obj)",
+" Return the current address of the object. For debugging only--this\n"
+" address may change during a moving GC."),
+
+ JS_FN_HELP("sharedAddress", SharedAddress, 1, 0,
+"sharedAddress(obj)",
+" Return the address of the shared storage of a SharedArrayBuffer."),
+#endif
+
+ JS_FN_HELP("evalReturningScope", EvalReturningScope, 1, 0,
+"evalReturningScope(scriptStr, [global])",
+" Evaluate the script in a new scope and return the scope.\n"
+" If |global| is present, clone the script to |global| before executing."),
+
+ JS_FN_HELP("cloneAndExecuteScript", ShellCloneAndExecuteScript, 2, 0,
+"cloneAndExecuteScript(source, global)",
+" Compile |source| in the current compartment, clone it into |global|'s\n"
+" compartment, and run it there."),
+
+ JS_FN_HELP("backtrace", DumpBacktrace, 1, 0,
+"backtrace()",
+" Dump out a brief backtrace."),
+
+ JS_FN_HELP("getBacktrace", GetBacktrace, 1, 0,
+"getBacktrace([options])",
+" Return the current stack as a string. Takes an optional options object,\n"
+" which may contain any or all of the boolean properties\n"
+" options.args - show arguments to each function\n"
+" options.locals - show local variables in each frame\n"
+" options.thisprops - show the properties of the 'this' object of each frame\n"),
+
+ JS_FN_HELP("byteSize", ByteSize, 1, 0,
+"byteSize(value)",
+" Return the size in bytes occupied by |value|, or |undefined| if value\n"
+" is not allocated in memory.\n"),
+
+ JS_FN_HELP("byteSizeOfScript", ByteSizeOfScript, 1, 0,
+"byteSizeOfScript(f)",
+" Return the size in bytes occupied by the function |f|'s JSScript.\n"),
+
+ JS_FN_HELP("setImmutablePrototype", SetImmutablePrototype, 1, 0,
+"setImmutablePrototype(obj)",
+" Try to make obj's [[Prototype]] immutable, such that subsequent attempts to\n"
+" change it will fail. Return true if obj's [[Prototype]] was successfully made\n"
+" immutable (or if it already was immutable), false otherwise. Throws in case\n"
+" of internal error, or if the operation doesn't even make sense (for example,\n"
+" because the object is a revoked proxy)."),
+
+#ifdef DEBUG
+ JS_FN_HELP("dumpStringRepresentation", DumpStringRepresentation, 1, 0,
+"dumpStringRepresentation(str)",
+" Print a human-readable description of how the string |str| is represented.\n"),
+#endif
+
+ JS_FN_HELP("setLazyParsingDisabled", SetLazyParsingDisabled, 1, 0,
+"setLazyParsingDisabled(bool)",
+" Explicitly disable lazy parsing in the current compartment. The default is that lazy "
+" parsing is not explicitly disabled."),
+
+ JS_FN_HELP("setDiscardSource", SetDiscardSource, 1, 0,
+"setDiscardSource(bool)",
+" Explicitly enable source discarding in the current compartment. The default is that "
+" source discarding is not explicitly enabled."),
+
+ JS_FN_HELP("getConstructorName", GetConstructorName, 1, 0,
+"getConstructorName(object)",
+" If the given object was created with `new Ctor`, return the constructor's display name. "
+" Otherwise, return null."),
+
+ JS_FN_HELP("allocationMarker", AllocationMarker, 0, 0,
+"allocationMarker([options])",
+" Return a freshly allocated object whose [[Class]] name is\n"
+" \"AllocationMarker\". Such objects are allocated only by calls\n"
+" to this function, never implicitly by the system, making them\n"
+" suitable for use in allocation tooling tests. Takes an optional\n"
+" options object which may contain the following properties:\n"
+" * nursery: bool, whether to allocate the object in the nursery\n"),
+
+ JS_FN_HELP("setGCCallback", SetGCCallback, 1, 0,
+"setGCCallback({action:\"...\", options...})",
+" Set the GC callback. action may be:\n"
+" 'minorGC' - run a nursery collection\n"
+" 'majorGC' - run a major collection, nesting up to a given 'depth'\n"),
+
+ JS_FN_HELP("getLcovInfo", GetLcovInfo, 1, 0,
+"getLcovInfo(global)",
+" Generate LCOV tracefile for the given compartment. If no global are provided then\n"
+" the current global is used as the default one.\n"),
+
+#ifdef DEBUG
+ JS_FN_HELP("setRNGState", SetRNGState, 2, 0,
+"setRNGState(seed0, seed1)",
+" Set this compartment's RNG state.\n"),
+#endif
+
+ JS_FN_HELP("getModuleEnvironmentNames", GetModuleEnvironmentNames, 1, 0,
+"getModuleEnvironmentNames(module)",
+" Get the list of a module environment's bound names for a specified module.\n"),
+
+ JS_FN_HELP("getModuleEnvironmentValue", GetModuleEnvironmentValue, 2, 0,
+"getModuleEnvironmentValue(module, name)",
+" Get the value of a bound name in a module environment.\n"),
+
+ JS_FN_HELP("isConstructor", IsConstructor, 1, 0,
+"isConstructor(value)",
+" Returns whether the value is considered IsConstructor.\n"),
+
+ JS_FS_HELP_END
+};
+
+static const JSFunctionSpecWithHelp FuzzingUnsafeTestingFunctions[] = {
+#ifdef DEBUG
+ JS_FN_HELP("parseRegExp", ParseRegExp, 3, 0,
+"parseRegExp(pattern[, flags[, match_only])",
+" Parses a RegExp pattern and returns a tree, potentially throwing."),
+
+ JS_FN_HELP("disRegExp", DisRegExp, 3, 0,
+"disRegExp(regexp[, match_only[, input]])",
+" Dumps RegExp bytecode."),
+#endif
+
+ JS_FS_HELP_END
+};
+
+static const JSPropertySpec TestingProperties[] = {
+ JS_PSG("timesAccessed", TimesAccessed, 0),
+ JS_PS_END
+};
+
+bool
+js::DefineTestingFunctions(JSContext* cx, HandleObject obj, bool fuzzingSafe_,
+ bool disableOOMFunctions_)
+{
+ fuzzingSafe = fuzzingSafe_;
+ if (EnvVarIsDefined("MOZ_FUZZING_SAFE"))
+ fuzzingSafe = true;
+
+ disableOOMFunctions = disableOOMFunctions_;
+
+ if (!JS_DefineProperties(cx, obj, TestingProperties))
+ return false;
+
+ if (!fuzzingSafe) {
+ if (!JS_DefineFunctionsWithHelp(cx, obj, FuzzingUnsafeTestingFunctions))
+ return false;
+ }
+
+ return JS_DefineFunctionsWithHelp(cx, obj, TestingFunctions);
+}
diff --git a/js/src/builtin/TestingFunctions.h b/js/src/builtin/TestingFunctions.h
new file mode 100644
index 000000000..e60b7d4db
--- /dev/null
+++ b/js/src/builtin/TestingFunctions.h
@@ -0,0 +1,25 @@
+/* -*- 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/. */
+
+#ifndef builtin_TestingFunctions_h
+#define builtin_TestingFunctions_h
+
+#include "NamespaceImports.h"
+
+namespace js {
+
+MOZ_MUST_USE bool
+DefineTestingFunctions(JSContext* cx, HandleObject obj, bool fuzzingSafe, bool disableOOMFunctions);
+
+MOZ_MUST_USE bool
+testingFunc_assertFloat32(JSContext* cx, unsigned argc, Value* vp);
+
+MOZ_MUST_USE bool
+testingFunc_assertRecoveredOnBailout(JSContext* cx, unsigned argc, Value* vp);
+
+} /* namespace js */
+
+#endif /* builtin_TestingFunctions_h */
diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js
new file mode 100644
index 000000000..4d2d6488f
--- /dev/null
+++ b/js/src/builtin/TypedArray.js
@@ -0,0 +1,1735 @@
+/* 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 "TypedObjectConstants.h"
+
+function ViewedArrayBufferIfReified(tarray) {
+ assert(IsTypedArray(tarray), "non-typed array asked for its buffer");
+
+ var buf = UnsafeGetReservedSlot(tarray, JS_TYPEDARRAYLAYOUT_BUFFER_SLOT);
+ assert(buf === null || (IsObject(buf) && (IsArrayBuffer(buf) || IsSharedArrayBuffer(buf))),
+ "unexpected value in buffer slot");
+ return buf;
+}
+
+function IsDetachedBuffer(buffer) {
+ // A typed array with a null buffer has never had its buffer exposed to
+ // become detached.
+ if (buffer === null)
+ return false;
+
+ assert(IsArrayBuffer(buffer) || IsSharedArrayBuffer(buffer),
+ "non-ArrayBuffer passed to IsDetachedBuffer");
+
+ // Shared array buffers are not detachable.
+ //
+ // This check is more expensive than desirable, but IsDetachedBuffer is
+ // only hot for non-shared memory in SetFromNonTypedArray, so there is an
+ // optimization in place there to avoid incurring the cost here. An
+ // alternative is to give SharedArrayBuffer the same layout as ArrayBuffer.
+ if (IsSharedArrayBuffer(buffer))
+ return false;
+
+ var flags = UnsafeGetInt32FromReservedSlot(buffer, JS_ARRAYBUFFER_FLAGS_SLOT);
+ return (flags & JS_ARRAYBUFFER_DETACHED_FLAG) !== 0;
+}
+
+function GetAttachedArrayBuffer(tarray) {
+ var buffer = ViewedArrayBufferIfReified(tarray);
+ if (IsDetachedBuffer(buffer))
+ ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
+ return buffer;
+}
+
+// A function which ensures that the argument is either a typed array or a
+// cross-compartment wrapper for a typed array and that the typed array involved
+// has an attached array buffer. If one of those conditions doesn't hold (wrong
+// kind of argument, or detached array buffer), an exception is thrown. The
+// return value is `true` if the argument is a typed array, `false` if it's a
+// cross-compartment wrapper for a typed array.
+function IsTypedArrayEnsuringArrayBuffer(arg) {
+ if (IsObject(arg) && IsTypedArray(arg)) {
+ GetAttachedArrayBuffer(arg);
+ return true;
+ }
+
+ // This is a bit hacky but gets the job done: the first `arg` is used to
+ // test for a wrapped typed array, the second as an argument to
+ // GetAttachedArrayBuffer.
+ callFunction(CallTypedArrayMethodIfWrapped, arg, arg, "GetAttachedArrayBuffer");
+ return false;
+}
+
+// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
+// 22.2.3.5.1 Runtime Semantics: ValidateTypedArray ( O )
+function ValidateTypedArray(obj, error) {
+ if (IsObject(obj)) {
+ /* Steps 3-5 (non-wrapped typed arrays). */
+ if (IsTypedArray(obj)) {
+ // GetAttachedArrayBuffer throws for detached array buffers.
+ GetAttachedArrayBuffer(obj);
+ return true;
+ }
+
+ /* Steps 3-5 (wrapped typed arrays). */
+ if (IsPossiblyWrappedTypedArray(obj)) {
+ if (PossiblyWrappedTypedArrayHasDetachedBuffer(obj))
+ ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
+ return false;
+ }
+ }
+
+ /* Steps 1-2. */
+ ThrowTypeError(error);
+}
+
+// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
+// 22.2.4.6 TypedArrayCreate ( constructor, argumentList )
+function TypedArrayCreateWithLength(constructor, length) {
+ // Step 1.
+ var newTypedArray = new constructor(length);
+
+ // Step 2.
+ var isTypedArray = ValidateTypedArray(newTypedArray, JSMSG_NON_TYPED_ARRAY_RETURNED);
+
+ // Step 3.
+ var len;
+ if (isTypedArray) {
+ len = TypedArrayLength(newTypedArray);
+ } else {
+ len = callFunction(CallTypedArrayMethodIfWrapped, newTypedArray, newTypedArray,
+ "TypedArrayLength");
+ }
+
+ if (len < length)
+ ThrowTypeError(JSMSG_SHORT_TYPED_ARRAY_RETURNED, length, len);
+
+ // Step 4.
+ return newTypedArray;
+}
+
+// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
+// 22.2.4.6 TypedArrayCreate ( constructor, argumentList )
+function TypedArrayCreateWithBuffer(constructor, buffer, byteOffset, length) {
+ // Step 1.
+ var newTypedArray = new constructor(buffer, byteOffset, length);
+
+ // Step 2.
+ ValidateTypedArray(newTypedArray, JSMSG_NON_TYPED_ARRAY_RETURNED);
+
+ // Step 3 (not applicable).
+
+ // Step 4.
+ return newTypedArray;
+}
+
+// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
+// 22.2.4.7 TypedArraySpeciesCreate ( exemplar, argumentList )
+function TypedArraySpeciesCreateWithLength(exemplar, length) {
+ // Step 1 (omitted).
+
+ // Step 2.
+ var defaultConstructor = _ConstructorForTypedArray(exemplar);
+
+ // Step 3.
+ var C = SpeciesConstructor(exemplar, defaultConstructor);
+
+ // Step 4.
+ return TypedArrayCreateWithLength(C, length);
+}
+
+// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
+// 22.2.4.7 TypedArraySpeciesCreate ( exemplar, argumentList )
+function TypedArraySpeciesCreateWithBuffer(exemplar, buffer, byteOffset, length) {
+ // Step 1 (omitted).
+
+ // Step 2.
+ var defaultConstructor = _ConstructorForTypedArray(exemplar);
+
+ // Step 3.
+ var C = SpeciesConstructor(exemplar, defaultConstructor);
+
+ // Step 4.
+ return TypedArrayCreateWithBuffer(C, buffer, byteOffset, length);
+}
+
+// ES6 draft 20150304 %TypedArray%.prototype.copyWithin
+function TypedArrayCopyWithin(target, start, end = undefined) {
+ // This function is not generic.
+ if (!IsObject(this) || !IsTypedArray(this)) {
+ return callFunction(CallTypedArrayMethodIfWrapped, this, target, start, end,
+ "TypedArrayCopyWithin");
+ }
+
+ GetAttachedArrayBuffer(this);
+
+ // Steps 1-2.
+ var obj = this;
+
+ // Steps 3-4, modified for the typed array case.
+ var len = TypedArrayLength(obj);
+
+ assert(0 <= len && len <= 0x7FFFFFFF,
+ "assumed by some of the math below, see also the other assertions");
+
+ // Steps 5-7.
+ var relativeTarget = ToInteger(target);
+
+ var to = relativeTarget < 0 ? std_Math_max(len + relativeTarget, 0)
+ : std_Math_min(relativeTarget, len);
+
+ // Steps 8-10.
+ var relativeStart = ToInteger(start);
+
+ var from = relativeStart < 0 ? std_Math_max(len + relativeStart, 0)
+ : std_Math_min(relativeStart, len);
+
+ // Steps 11-13.
+ var relativeEnd = end === undefined ? len
+ : ToInteger(end);
+
+ var final = relativeEnd < 0 ? std_Math_max(len + relativeEnd, 0)
+ : std_Math_min(relativeEnd, len);
+
+ // Step 14.
+ var count = std_Math_min(final - from, len - to);
+
+ assert(0 <= to && to <= 0x7FFFFFFF,
+ "typed array |to| index assumed int32_t");
+ assert(0 <= from && from <= 0x7FFFFFFF,
+ "typed array |from| index assumed int32_t");
+
+ // Negative counts are possible for cases like tarray.copyWithin(0, 3, 0)
+ // where |count === final - from|. As |to| is within the [0, len] range,
+ // only |final - from| may underflow; with |final| in the range [0, len]
+ // and |from| in the range [0, len] the overall subtraction range is
+ // [-len, len] for |count| -- and with |len| bounded by implementation
+ // limits to 2**31 - 1, there can be no exceeding int32_t.
+ assert(-0x7FFFFFFF - 1 <= count && count <= 0x7FFFFFFF,
+ "typed array element count assumed int32_t");
+
+ // Steps 15-17.
+ //
+ // Note that getting or setting a typed array element must throw if the
+ // underlying buffer is detached, so the intrinsic below checks for
+ // detachment. This happens *only* if a get/set occurs, i.e. when
+ // |count > 0|.
+ //
+ // Also note that this copies elements effectively by memmove, *not* in
+ // step 17's specified order. This is unobservable, but it would be if we
+ // used this method to implement shared typed arrays' copyWithin.
+ if (count > 0)
+ MoveTypedArrayElements(obj, to | 0, from | 0, count | 0);
+
+ // Step 18.
+ return obj;
+}
+
+// ES6 draft rev30 (2014/12/24) 22.2.3.6 %TypedArray%.prototype.entries()
+function TypedArrayEntries() {
+ // Step 1.
+ var O = this;
+
+ // We need to be a bit careful here, because in the Xray case we want to
+ // create the iterator in our current compartment.
+ //
+ // Before doing that, though, we want to check that we have a typed array
+ // and it does not have a detached array buffer. We do the latter by just
+ // calling GetAttachedArrayBuffer() and letting it throw if there isn't one.
+ // In the case when we're not sure we have a typed array (e.g. we might have
+ // a cross-compartment wrapper for one), we can go ahead and call
+ // GetAttachedArrayBuffer via IsTypedArrayEnsuringArrayBuffer; that will
+ // throw if we're not actually a wrapped typed array, or if we have a
+ // detached array buffer.
+
+ // Step 2-6.
+ IsTypedArrayEnsuringArrayBuffer(O);
+
+ // Step 7.
+ return CreateArrayIterator(O, ITEM_KIND_KEY_AND_VALUE);
+}
+
+// ES6 draft rev30 (2014/12/24) 22.2.3.7 %TypedArray%.prototype.every(callbackfn[, thisArg]).
+function TypedArrayEvery(callbackfn/*, thisArg*/) {
+ // Steps 1-2.
+ var O = this;
+
+ // This function is not generic.
+ // We want to make sure that we have an attached buffer, per spec prose.
+ var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+ // If we got here, `this` is either a typed array or a cross-compartment
+ // wrapper for one.
+
+ // Steps 3-5.
+ var len;
+ if (isTypedArray)
+ len = TypedArrayLength(O);
+ else
+ len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+
+ // Step 6.
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.every");
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ // Step 7.
+ var T = arguments.length > 1 ? arguments[1] : void 0;
+
+ // Steps 8-9.
+ // Omit steps 9.a-9.c and the 'if' clause in step 9.d, since there are no holes in typed arrays.
+ for (var k = 0; k < len; k++) {
+ // Steps 9.d.i-9.d.ii.
+ var kValue = O[k];
+
+ // Steps 9.d.iii-9.d.iv.
+ var testResult = callContentFunction(callbackfn, T, kValue, k, O);
+
+ // Step 9.d.v.
+ if (!testResult)
+ return false;
+ }
+
+ // Step 10.
+ return true;
+}
+
+// ES6 draft rev29 (2014/12/06) 22.2.3.8 %TypedArray%.prototype.fill(value [, start [, end ]])
+function TypedArrayFill(value, start = 0, end = undefined) {
+ // This function is not generic.
+ if (!IsObject(this) || !IsTypedArray(this)) {
+ return callFunction(CallTypedArrayMethodIfWrapped, this, value, start, end,
+ "TypedArrayFill");
+ }
+
+ GetAttachedArrayBuffer(this);
+
+ // Steps 1-2.
+ var O = this;
+
+ // Steps 3-5.
+ var len = TypedArrayLength(O);
+
+ // Steps 6-7.
+ var relativeStart = ToInteger(start);
+
+ // Step 8.
+ var k = relativeStart < 0
+ ? std_Math_max(len + relativeStart, 0)
+ : std_Math_min(relativeStart, len);
+
+ // Steps 9-10.
+ var relativeEnd = end === undefined ? len : ToInteger(end);
+
+ // Step 11.
+ var final = relativeEnd < 0
+ ? std_Math_max(len + relativeEnd, 0)
+ : std_Math_min(relativeEnd, len);
+
+ // Step 12.
+ for (; k < final; k++) {
+ O[k] = value;
+ }
+
+ // Step 13.
+ return O;
+}
+
+// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
+// %TypedArray%.prototype.filter ( callbackfn [ , thisArg ] )
+function TypedArrayFilter(callbackfn/*, thisArg*/) {
+ // Step 1.
+ var O = this;
+
+ // Step 2.
+ // This function is not generic.
+ // We want to make sure that we have an attached buffer, per spec prose.
+ var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+ // If we got here, `this` is either a typed array or a cross-compartment
+ // wrapper for one.
+
+ // Step 3.
+ var len;
+ if (isTypedArray)
+ len = TypedArrayLength(O);
+ else
+ len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+
+ // Step 4.
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.filter");
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ // Step 5.
+ var T = arguments.length > 1 ? arguments[1] : void 0;
+
+ // Step 6.
+ var kept = new List();
+
+ // Step 8.
+ var captured = 0;
+
+ // Steps 7 and 9.e.
+ for (var k = 0; k < len; k++) {
+ // Steps 9.a-b.
+ var kValue = O[k];
+
+ // Step 9.c.
+ var selected = ToBoolean(callContentFunction(callbackfn, T, kValue, k, O));
+
+ // Step 9.d.
+ if (selected) {
+ // Steps 9.d.i-ii.
+ kept[captured++] = kValue;
+ }
+ }
+
+ // Step 10.
+ var A = TypedArraySpeciesCreateWithLength(O, captured);
+
+ // Steps 11 and 12.b.
+ for (var n = 0; n < captured; n++) {
+ // Step 12.a.
+ A[n] = kept[n];
+ }
+
+ // Step 13.
+ return A;
+}
+
+// ES6 draft rev28 (2014/10/14) 22.2.3.10 %TypedArray%.prototype.find(predicate[, thisArg]).
+function TypedArrayFind(predicate/*, thisArg*/) {
+ // Steps 1-2.
+ var O = this;
+
+ // This function is not generic.
+ // We want to make sure that we have an attached buffer, per spec prose.
+ var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+ // If we got here, `this` is either a typed array or a cross-compartment
+ // wrapper for one.
+
+ // Steps 3-5.
+ var len;
+ if (isTypedArray)
+ len = TypedArrayLength(O);
+ else
+ len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+
+ // Step 6.
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.find");
+ if (!IsCallable(predicate))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, predicate));
+
+ // Step 7.
+ var T = arguments.length > 1 ? arguments[1] : void 0;
+
+ // Steps 8-9.
+ // Steps a (implicit), and g.
+ for (var k = 0; k < len; k++) {
+ // Steps a-c.
+ var kValue = O[k];
+ // Steps d-f.
+ if (callContentFunction(predicate, T, kValue, k, O))
+ return kValue;
+ }
+
+ // Step 10.
+ return undefined;
+}
+
+// ES6 draft rev28 (2014/10/14) 22.2.3.11 %TypedArray%.prototype.findIndex(predicate[, thisArg]).
+function TypedArrayFindIndex(predicate/*, thisArg*/) {
+ // Steps 1-2.
+ var O = this;
+
+ // This function is not generic.
+ // We want to make sure that we have an attached buffer, per spec prose.
+ var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+ // If we got here, `this` is either a typed array or a cross-compartment
+ // wrapper for one.
+
+ // Steps 3-5.
+ var len;
+ if (isTypedArray)
+ len = TypedArrayLength(O);
+ else
+ len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+
+ // Step 6.
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.findIndex");
+ if (!IsCallable(predicate))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, predicate));
+
+ // Step 7.
+ var T = arguments.length > 1 ? arguments[1] : void 0;
+
+ // Steps 8-9.
+ // Steps a (implicit), and g.
+ for (var k = 0; k < len; k++) {
+ // Steps a-f.
+ if (callContentFunction(predicate, T, O[k], k, O))
+ return k;
+ }
+
+ // Step 10.
+ return -1;
+}
+
+// ES6 draft rev31 (2015-01-15) 22.1.3.10 %TypedArray%.prototype.forEach(callbackfn[,thisArg])
+function TypedArrayForEach(callbackfn/*, thisArg*/) {
+ // Step 1-2.
+ var O = this;
+
+ // This function is not generic.
+ // We want to make sure that we have an attached buffer, per spec prose.
+ var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+ // If we got here, `this` is either a typed array or a cross-compartment
+ // wrapper for one.
+
+ // Step 3-4.
+ var len;
+ if (isTypedArray)
+ len = TypedArrayLength(O);
+ else
+ len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+
+ // Step 5.
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'TypedArray.prototype.forEach');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ // Step 6.
+ var T = arguments.length > 1 ? arguments[1] : void 0;
+
+ // Step 7-8.
+ // Step 7, 8a (implicit) and 8e.
+ for (var k = 0; k < len; k++) {
+ // Step 8b-8c are unnecessary since the condition always holds true for TypedArray.
+ // Step 8d.
+ callContentFunction(callbackfn, T, O[k], k, O);
+ }
+
+ // Step 9.
+ return undefined;
+}
+
+// ES6 draft rev29 (2014/12/06) 22.2.3.13 %TypedArray%.prototype.indexOf(searchElement[, fromIndex]).
+function TypedArrayIndexOf(searchElement, fromIndex = 0) {
+ // This function is not generic.
+ if (!IsObject(this) || !IsTypedArray(this)) {
+ return callFunction(CallTypedArrayMethodIfWrapped, this, searchElement, fromIndex,
+ "TypedArrayIndexOf");
+ }
+
+ GetAttachedArrayBuffer(this);
+
+ // Steps 1-2.
+ var O = this;
+
+ // Steps 3-5.
+ var len = TypedArrayLength(O);
+
+ // Step 6.
+ if (len === 0)
+ return -1;
+
+ // Steps 7-8. Add zero to convert -0 to +0, per ES6 5.2.
+ var n = ToInteger(fromIndex) + 0;
+
+ // Step 9.
+ if (n >= len)
+ return -1;
+
+ var k;
+ // Step 10.
+ if (n >= 0) {
+ k = n;
+ }
+ // Step 11.
+ else {
+ // Step a.
+ k = len + n;
+ // Step b.
+ if (k < 0)
+ k = 0;
+ }
+
+ // Step 12.
+ // Omit steps a-b, since there are no holes in typed arrays.
+ for (; k < len; k++) {
+ if (O[k] === searchElement)
+ return k;
+ }
+
+ // Step 13.
+ return -1;
+}
+
+// ES6 draft rev30 (2014/12/24) 22.2.3.14 %TypedArray%.prototype.join(separator).
+function TypedArrayJoin(separator) {
+ // This function is not generic.
+ if (!IsObject(this) || !IsTypedArray(this)) {
+ return callFunction(CallTypedArrayMethodIfWrapped, this, separator, "TypedArrayJoin");
+ }
+
+ GetAttachedArrayBuffer(this);
+
+ // Steps 1-2.
+ var O = this;
+
+ // Steps 3-5.
+ var len = TypedArrayLength(O);
+
+ // Steps 6-7.
+ var sep = separator === undefined ? "," : ToString(separator);
+
+ // Step 8.
+ if (len === 0)
+ return "";
+
+ // Step 9.
+ var element0 = O[0];
+
+ // Steps 10-11.
+ // Omit the 'if' clause in step 10, since typed arrays can't have undefined or null elements.
+ var R = ToString(element0);
+
+ // Steps 12-13.
+ for (var k = 1; k < len; k++) {
+ // Step 13.a.
+ var S = R + sep;
+
+ // Step 13.b.
+ var element = O[k];
+
+ // Steps 13.c-13.d.
+ // Omit the 'if' clause in step 13.c, since typed arrays can't have undefined or null elements.
+ var next = ToString(element);
+
+ // Step 13.e.
+ R = S + next;
+ }
+
+ // Step 14.
+ return R;
+}
+
+// ES6 draft (2016/1/11) 22.2.3.15 %TypedArray%.prototype.keys()
+function TypedArrayKeys() {
+ // Step 1.
+ var O = this;
+
+ // See the big comment in TypedArrayEntries for what we're doing here.
+
+ // Step 2.
+ IsTypedArrayEnsuringArrayBuffer(O);
+
+ // Step 3.
+ return CreateArrayIterator(O, ITEM_KIND_KEY);
+}
+
+// ES6 draft rev29 (2014/12/06) 22.2.3.16 %TypedArray%.prototype.lastIndexOf(searchElement [,fromIndex]).
+function TypedArrayLastIndexOf(searchElement, fromIndex = undefined) {
+ // This function is not generic.
+ if (!IsObject(this) || !IsTypedArray(this)) {
+ return callFunction(CallTypedArrayMethodIfWrapped, this, searchElement, fromIndex,
+ "TypedArrayLastIndexOf");
+ }
+
+ GetAttachedArrayBuffer(this);
+
+ // Steps 1-2.
+ var O = this;
+
+ // Steps 3-5.
+ var len = TypedArrayLength(O);
+
+ // Step 6.
+ if (len === 0)
+ return -1;
+
+ // Steps 7-8. Add zero to convert -0 to +0, per ES6 5.2.
+ var n = fromIndex === undefined ? len - 1 : ToInteger(fromIndex) + 0;
+
+ // Steps 9-10.
+ var k = n >= 0 ? std_Math_min(n, len - 1) : len + n;
+
+ // Step 11.
+ // Omit steps a-b, since there are no holes in typed arrays.
+ for (; k >= 0; k--) {
+ if (O[k] === searchElement)
+ return k;
+ }
+
+ // Step 12.
+ return -1;
+}
+
+// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
+// 22.2.3.19 %TypedArray%.prototype.map ( callbackfn [ , thisArg ] )
+function TypedArrayMap(callbackfn/*, thisArg*/) {
+ // Step 1.
+ var O = this;
+
+ // Step 2.
+ // This function is not generic.
+ // We want to make sure that we have an attached buffer, per spec prose.
+ var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+ // If we got here, `this` is either a typed array or a cross-compartment
+ // wrapper for one.
+
+ // Step 3.
+ var len;
+ if (isTypedArray)
+ len = TypedArrayLength(O);
+ else
+ len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+
+ // Step 4.
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, '%TypedArray%.prototype.map');
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ // Step 5.
+ var T = arguments.length > 1 ? arguments[1] : void 0;
+
+ // Step 6.
+ var A = TypedArraySpeciesCreateWithLength(O, len);
+
+ // Steps 7, 8.a (implicit) and 8.e.
+ for (var k = 0; k < len; k++) {
+ // Steps 8.b-c.
+ var mappedValue = callContentFunction(callbackfn, T, O[k], k, O);
+
+ // Steps 8.d.
+ A[k] = mappedValue;
+ }
+
+ // Step 9.
+ return A;
+}
+
+// ES6 draft rev30 (2014/12/24) 22.2.3.19 %TypedArray%.prototype.reduce(callbackfn[, initialValue]).
+function TypedArrayReduce(callbackfn/*, initialValue*/) {
+ // Steps 1-2.
+ var O = this;
+
+ // This function is not generic.
+ // We want to make sure that we have an attached buffer, per spec prose.
+ var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+ // If we got here, `this` is either a typed array or a cross-compartment
+ // wrapper for one.
+
+ // Steps 3-5.
+ var len;
+ if (isTypedArray)
+ len = TypedArrayLength(O);
+ else
+ len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+
+ // Step 6.
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.reduce");
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ // Step 7.
+ if (len === 0 && arguments.length === 1)
+ ThrowTypeError(JSMSG_EMPTY_ARRAY_REDUCE);
+
+ // Step 8.
+ var k = 0;
+
+ // Steps 9-10.
+ // Omit some steps, since 'accumulator' should always be O[0] in step 10 for typed arrays.
+ var accumulator = arguments.length > 1 ? arguments[1] : O[k++];
+
+ // Step 11.
+ // Omit steps 11.b-11.c and the 'if' clause in step 11.d, since there are no holes in typed arrays.
+ for (; k < len; k++) {
+ accumulator = callContentFunction(callbackfn, undefined, accumulator, O[k], k, O);
+ }
+
+ // Step 12.
+ return accumulator;
+}
+
+// ES6 draft rev30 (2014/12/24) 22.2.3.20 %TypedArray%.prototype.reduceRight(callbackfn[, initialValue]).
+function TypedArrayReduceRight(callbackfn/*, initialValue*/) {
+ // Steps 1-2.
+ var O = this;
+
+ // This function is not generic.
+ // We want to make sure that we have an attached buffer, per spec prose.
+ var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+ // If we got here, `this` is either a typed array or a cross-compartment
+ // wrapper for one.
+
+ // Steps 3-5.
+ var len;
+ if (isTypedArray)
+ len = TypedArrayLength(O);
+ else
+ len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+
+ // Step 6.
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.reduceRight");
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ // Step 7.
+ if (len === 0 && arguments.length === 1)
+ ThrowTypeError(JSMSG_EMPTY_ARRAY_REDUCE);
+
+ // Step 8.
+ var k = len - 1;
+
+ // Steps 9-10.
+ // Omit some steps, since 'accumulator' should always be O[len-1] in step 10 for typed arrays.
+ var accumulator = arguments.length > 1 ? arguments[1] : O[k--];
+
+ // Step 11.
+ // Omit steps 11.b-11.c and the 'if' clause in step 11.d, since there are no holes in typed arrays.
+ for (; k >= 0; k--) {
+ accumulator = callContentFunction(callbackfn, undefined, accumulator, O[k], k, O);
+ }
+
+ // Step 12.
+ return accumulator;
+}
+
+// ES6 draft rev29 (2014/12/06) 22.2.3.21 %TypedArray%.prototype.reverse().
+function TypedArrayReverse() {
+ // This function is not generic.
+ if (!IsObject(this) || !IsTypedArray(this)) {
+ return callFunction(CallTypedArrayMethodIfWrapped, this, "TypedArrayReverse");
+ }
+
+ GetAttachedArrayBuffer(this);
+
+ // Steps 1-2.
+ var O = this;
+
+ // Steps 3-5.
+ var len = TypedArrayLength(O);
+
+ // Step 6.
+ var middle = std_Math_floor(len / 2);
+
+ // Steps 7-8.
+ // Omit some steps, since there are no holes in typed arrays.
+ // Especially all the HasProperty/*exists checks always succeed.
+ for (var lower = 0; lower !== middle; lower++) {
+ // Step 8.a.
+ var upper = len - lower - 1;
+
+ // Step 8.f.i.
+ var lowerValue = O[lower];
+
+ // Step 8.i.i.
+ var upperValue = O[upper];
+
+ // We always end up in the step 8.j. case.
+ O[lower] = upperValue;
+ O[upper] = lowerValue;
+ }
+
+ // Step 9.
+ return O;
+}
+
+// ES6 draft 20150220 22.2.3.22.1 %TypedArray%.prototype.set(array [, offset])
+function SetFromNonTypedArray(target, array, targetOffset, targetLength, targetBuffer) {
+ assert(!IsPossiblyWrappedTypedArray(array),
+ "typed arrays must be passed to SetFromTypedArray");
+
+ // Steps 1-11 provided by caller.
+
+ // Steps 16-17.
+ var src = ToObject(array);
+
+ // Steps 18-19.
+ var srcLength = ToLength(src.length);
+
+ // Step 20.
+ var limitOffset = targetOffset + srcLength;
+ if (limitOffset > targetLength)
+ ThrowRangeError(JSMSG_BAD_INDEX);
+
+ // Step 22.
+ var k = 0;
+
+ // Optimization: if the buffer is shared then it is not detachable
+ // and also not inline, so avoid checking overhead inside the loop in
+ // that case.
+ var isShared = targetBuffer !== null && IsSharedArrayBuffer(targetBuffer);
+
+ // Steps 12-15, 21, 23-24.
+ while (targetOffset < limitOffset) {
+ // Steps 24a-c.
+ var kNumber = ToNumber(src[k]);
+
+ // Step 24d. This explicit check will be unnecessary when we implement
+ // throw-on-getting/setting-element-in-detached-buffer semantics.
+ if (!isShared) {
+ if (targetBuffer === null) {
+ // A typed array previously using inline storage may acquire a
+ // buffer, so we must check with the source.
+ targetBuffer = ViewedArrayBufferIfReified(target);
+ }
+ if (IsDetachedBuffer(targetBuffer))
+ ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
+ }
+
+ // Step 24e.
+ target[targetOffset] = kNumber;
+
+ // Steps 24f-g.
+ k++;
+ targetOffset++;
+ }
+
+ // Step 25.
+ return undefined;
+}
+
+// ES6 draft 20150220 22.2.3.22.2 %TypedArray%.prototype.set(typedArray [, offset])
+function SetFromTypedArray(target, typedArray, targetOffset, targetLength) {
+ assert(IsPossiblyWrappedTypedArray(typedArray),
+ "only typed arrays may be passed to this method");
+
+ // Steps 1-11 provided by caller.
+
+ // Steps 12-24.
+ var res = SetFromTypedArrayApproach(target, typedArray, targetOffset,
+ targetLength | 0);
+ assert(res === JS_SETTYPEDARRAY_SAME_TYPE ||
+ res === JS_SETTYPEDARRAY_OVERLAPPING ||
+ res === JS_SETTYPEDARRAY_DISJOINT,
+ "intrinsic didn't return one of its enumerated return values");
+
+ // If the elements had the same type, then SetFromTypedArrayApproach also
+ // performed step 29.
+ if (res == JS_SETTYPEDARRAY_SAME_TYPE)
+ return undefined; // Step 25: done.
+
+ // Otherwise, all checks and side effects except the actual element-writing
+ // happened. Either we're assigning from one range to a non-overlapping
+ // second range, or we're not.
+
+ if (res === JS_SETTYPEDARRAY_DISJOINT) {
+ SetDisjointTypedElements(target, targetOffset | 0, typedArray);
+ return undefined; // Step 25: done.
+ }
+
+ // Now the hard case: overlapping memory ranges. Delegate to yet another
+ // intrinsic.
+ SetOverlappingTypedElements(target, targetOffset | 0, typedArray);
+
+ // Step 25.
+ return undefined;
+}
+
+// ES6 draft 20150304 %TypedArray%.prototype.set
+function TypedArraySet(overloaded, offset = 0) {
+ // Steps 2-5, either algorithm.
+ var target = this;
+ if (!IsObject(target) || !IsTypedArray(target)) {
+ return callFunction(CallTypedArrayMethodIfWrapped,
+ target, overloaded, offset, "TypedArraySet");
+ }
+
+ // Steps 6-8, either algorithm.
+ var targetOffset = ToInteger(offset);
+ if (targetOffset < 0)
+ ThrowRangeError(JSMSG_TYPED_ARRAY_NEGATIVE_ARG, "2");
+
+ // Steps 9-10.
+ var targetBuffer = GetAttachedArrayBuffer(target);
+
+ // Step 11.
+ var targetLength = TypedArrayLength(target);
+
+ // Steps 12 et seq.
+ if (IsPossiblyWrappedTypedArray(overloaded))
+ return SetFromTypedArray(target, overloaded, targetOffset, targetLength);
+
+ return SetFromNonTypedArray(target, overloaded, targetOffset, targetLength, targetBuffer);
+}
+
+// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
+// 22.2.3.24 %TypedArray%.prototype.slice ( start, end )
+function TypedArraySlice(start, end) {
+ // Step 1.
+ var O = this;
+
+ // Step 2.
+ if (!IsObject(O) || !IsTypedArray(O)) {
+ return callFunction(CallTypedArrayMethodIfWrapped, O, start, end, "TypedArraySlice");
+ }
+
+ GetAttachedArrayBuffer(O);
+
+ // Step 3.
+ var len = TypedArrayLength(O);
+
+ // Step 4.
+ var relativeStart = ToInteger(start);
+
+ // Step 5.
+ var k = relativeStart < 0
+ ? std_Math_max(len + relativeStart, 0)
+ : std_Math_min(relativeStart, len);
+
+ // Step 6.
+ var relativeEnd = end === undefined ? len : ToInteger(end);
+
+ // Step 7.
+ var final = relativeEnd < 0
+ ? std_Math_max(len + relativeEnd, 0)
+ : std_Math_min(relativeEnd, len);
+
+ // Step 8.
+ var count = std_Math_max(final - k, 0);
+
+ // Step 9.
+ var A = TypedArraySpeciesCreateWithLength(O, count);
+
+ // Step 14.a.
+ var n = 0;
+
+ // Step 14.b.
+ while (k < final) {
+ // Steps 14.b.i-v.
+ A[n++] = O[k++];
+ }
+
+ // FIXME: Implement step 15 (bug 1140152).
+
+ // Step 16.
+ return A;
+}
+
+// ES6 draft rev30 (2014/12/24) 22.2.3.25 %TypedArray%.prototype.some(callbackfn[, thisArg]).
+function TypedArraySome(callbackfn/*, thisArg*/) {
+ // Steps 1-2.
+ var O = this;
+
+ // This function is not generic.
+ // We want to make sure that we have an attached buffer, per spec prose.
+ var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+ // If we got here, `this` is either a typed array or a cross-compartment
+ // wrapper for one.
+
+ // Steps 3-5.
+ var len;
+ if (isTypedArray)
+ len = TypedArrayLength(O);
+ else
+ len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+
+ // Step 6.
+ if (arguments.length === 0)
+ ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.some");
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+ // Step 7.
+ var T = arguments.length > 1 ? arguments[1] : void 0;
+
+ // Steps 8-9.
+ // Omit steps 9.a-9.c and the 'if' clause in step 9.d, since there are no holes in typed arrays.
+ for (var k = 0; k < len; k++) {
+ // Steps 9.d.i-9.d.ii.
+ var kValue = O[k];
+
+ // Steps 9.d.iii-9.d.iv.
+ var testResult = callContentFunction(callbackfn, T, kValue, k, O);
+
+ // Step 9.d.v.
+ if (testResult)
+ return true;
+ }
+
+ // Step 10.
+ return false;
+}
+
+// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
+// 22.2.3.26 TypedArray SortCompare abstract operation
+// Cases are ordered according to likelihood of occurrence
+// as opposed to the ordering in the spec.
+function TypedArrayCompare(x, y) {
+ // Step 1.
+ assert(typeof x === "number" && typeof y === "number",
+ "x and y are not numbers.");
+
+ // Step 2 (Implemented in TypedArraySort).
+
+ // Step 6.
+ if (x < y)
+ return -1;
+
+ // Step 7.
+ if (x > y)
+ return 1;
+
+ // Steps 8-9.
+ if (x === 0 && y === 0)
+ return (1/x > 0 ? 1 : 0) - (1/y > 0 ? 1 : 0);
+
+ // Steps 3-4.
+ if (Number_isNaN(x))
+ return Number_isNaN(y) ? 0 : 1;
+
+ // Steps 5, 10.
+ return Number_isNaN(y) ? -1 : 0;
+}
+
+// TypedArray SortCompare specialization for integer values.
+function TypedArrayCompareInt(x, y) {
+ // Step 1.
+ assert(typeof x === "number" && typeof y === "number",
+ "x and y are not numbers.");
+ assert((x === (x|0) || x === (x>>>0)) && (y === (y|0) || y === (y>>>0)),
+ "x and y are not int32/uint32 numbers.");
+
+ // Step 2 (Implemented in TypedArraySort).
+
+ // Steps 6-7.
+ var diff = x - y;
+ if (diff)
+ return diff;
+
+ // Steps 3-5, 8-9 (Not applicable when sorting integer values).
+
+ // Step 10.
+ return 0;
+}
+
+// ES6 draft 20151210 22.2.3.26 %TypedArray%.prototype.sort ( comparefn ).
+function TypedArraySort(comparefn) {
+ // This function is not generic.
+
+ // Step 1.
+ var obj = this;
+
+ // Step 2.
+ var isTypedArray = IsObject(obj) && IsTypedArray(obj);
+
+ var buffer;
+ if (isTypedArray) {
+ buffer = GetAttachedArrayBuffer(obj);
+ } else {
+ buffer = callFunction(CallTypedArrayMethodIfWrapped, obj, obj, "GetAttachedArrayBuffer");
+ }
+
+ // Step 3.
+ var len;
+ if (isTypedArray) {
+ len = TypedArrayLength(obj);
+ } else {
+ len = callFunction(CallTypedArrayMethodIfWrapped, obj, obj, "TypedArrayLength");
+ }
+
+ if (comparefn === undefined) {
+ // CountingSort doesn't invoke the comparator function.
+ if (IsUint8TypedArray(obj)) {
+ return CountingSort(obj, len, false /* signed */);
+ } else if (IsInt8TypedArray(obj)) {
+ return CountingSort(obj, len, true /* signed */);
+ } else if (IsUint16TypedArray(obj)) {
+ return RadixSort(obj, len, buffer, 2 /* nbytes */, false /* signed */, false /* floating */, TypedArrayCompareInt);
+ } else if (IsInt16TypedArray(obj)) {
+ return RadixSort(obj, len, buffer, 2 /* nbytes */, true /* signed */, false /* floating */, TypedArrayCompareInt);
+ } else if (IsUint32TypedArray(obj)) {
+ return RadixSort(obj, len, buffer, 4 /* nbytes */, false /* signed */, false /* floating */, TypedArrayCompareInt);
+ } else if (IsInt32TypedArray(obj)) {
+ return RadixSort(obj, len, buffer, 4 /* nbytes */, true /* signed */, false /* floating */, TypedArrayCompareInt);
+ } else if (IsFloat32TypedArray(obj)) {
+ return RadixSort(obj, len, buffer, 4 /* nbytes */, true /* signed */, true /* floating */, TypedArrayCompare);
+ }
+ return QuickSort(obj, len, TypedArrayCompare);
+ }
+
+ // To satisfy step 2 from TypedArray SortCompare described in 22.2.3.26
+ // the user supplied comparefn is wrapped.
+ var wrappedCompareFn = comparefn;
+ comparefn = function(x, y) {
+ // Step a.
+ var v = wrappedCompareFn(x, y);
+ // Step b.
+ if (buffer === null) {
+ // A typed array previously using inline storage may acquire a
+ // buffer, so we must check with the source.
+ if (isTypedArray) {
+ buffer = GetAttachedArrayBuffer(obj);
+ } else {
+ buffer = callFunction(CallTypedArrayMethodIfWrapped, obj, obj,
+ "GetAttachedArrayBuffer");
+ }
+ }
+ var bufferDetached;
+ if (isTypedArray) {
+ bufferDetached = IsDetachedBuffer(buffer);
+ } else {
+ // This is totally cheating and only works because we know `obj`
+ // and `buffer` are same-compartment".
+ bufferDetached = callFunction(CallTypedArrayMethodIfWrapped, obj,
+ buffer, "IsDetachedBuffer");
+ }
+ if (bufferDetached)
+ ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
+ // Step c. is redundant, see:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1121937#c36
+ // Step d.
+ return v;
+ }
+
+ return QuickSort(obj, len, comparefn);
+}
+
+// ES2017 draft rev f8a9be8ea4bd97237d176907a1e3080dce20c68f
+// 22.2.3.28 %TypedArray%.prototype.toLocaleString ([ reserved1 [ , reserved2 ] ])
+// ES2017 Intl draft rev 78bbe7d1095f5ff3760ac4017ed366026e4cb276
+// 13.4.1 Array.prototype.toLocaleString ([ locales [ , options ]])
+function TypedArrayToLocaleString(locales = undefined, options = undefined) {
+ // ValidateTypedArray, then step 1.
+ var array = this;
+
+ // This function is not generic.
+ // We want to make sure that we have an attached buffer, per spec prose.
+ var isTypedArray = IsTypedArrayEnsuringArrayBuffer(array);
+
+ // If we got here, `this` is either a typed array or a cross-compartment
+ // wrapper for one.
+
+ // Step 2.
+ var len;
+ if (isTypedArray)
+ len = TypedArrayLength(array);
+ else
+ len = callFunction(CallTypedArrayMethodIfWrapped, array, array, "TypedArrayLength");
+
+ // Step 4.
+ if (len === 0)
+ return "";
+
+ // Step 5.
+ var firstElement = array[0];
+
+ // Steps 6-7.
+ // Omit the 'if' clause in step 6, since typed arrays can't have undefined
+ // or null elements.
+#if EXPOSE_INTL_API
+ var R = ToString(callContentFunction(firstElement.toLocaleString, firstElement, locales, options));
+#else
+ var R = ToString(callContentFunction(firstElement.toLocaleString, firstElement));
+#endif
+
+ // Step 3 (reordered).
+ // We don't (yet?) implement locale-dependent separators.
+ var separator = ",";
+
+ // Steps 8-9.
+ for (var k = 1; k < len; k++) {
+ // Step 9.a.
+ var S = R + separator;
+
+ // Step 9.b.
+ var nextElement = array[k];
+
+ // Step 9.c *should* be unreachable: typed array elements are numbers.
+ // But bug 1079853 means |nextElement| *could* be |undefined|, if the
+ // previous iteration's step 9.d or step 7 detached |array|'s buffer.
+ // Conveniently, if this happens, evaluating |nextElement.toLocaleString|
+ // throws the required TypeError, and the only observable difference is
+ // the error message. So despite bug 1079853, we can skip step 9.c.
+
+ // Step 9.d.
+#if EXPOSE_INTL_API
+ R = ToString(callContentFunction(nextElement.toLocaleString, nextElement, locales, options));
+#else
+ R = ToString(callContentFunction(nextElement.toLocaleString, nextElement));
+#endif
+
+ // Step 9.e.
+ R = S + R;
+ }
+
+ // Step 10.
+ return R;
+}
+
+// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
+// 22.2.3.27 %TypedArray%.prototype.subarray( begin, end )
+function TypedArraySubarray(begin, end) {
+ // Step 1.
+ var obj = this;
+
+ // Steps 2-3.
+ // This function is not generic.
+ if (!IsObject(obj) || !IsTypedArray(obj)) {
+ return callFunction(CallTypedArrayMethodIfWrapped, this, begin, end,
+ "TypedArraySubarray");
+ }
+
+ // Steps 4-6.
+ var buffer = TypedArrayBuffer(obj);
+ var srcLength = TypedArrayLength(obj);
+
+ // Step 14 (Reordered because otherwise it'd be observable that we reset
+ // the byteOffset to zero when the underlying array buffer gets detached).
+ var srcByteOffset = TypedArrayByteOffset(obj);
+
+ // Steps 7-8.
+ var relativeBegin = ToInteger(begin);
+ var beginIndex = relativeBegin < 0 ? std_Math_max(srcLength + relativeBegin, 0)
+ : std_Math_min(relativeBegin, srcLength);
+
+ // Steps 9-10.
+ var relativeEnd = end === undefined ? srcLength : ToInteger(end);
+ var endIndex = relativeEnd < 0 ? std_Math_max(srcLength + relativeEnd, 0)
+ : std_Math_min(relativeEnd, srcLength);
+
+ // Step 11.
+ var newLength = std_Math_max(endIndex - beginIndex, 0);
+
+ // Steps 12-13, altered to use a shift instead of a size for performance.
+ var elementShift = TypedArrayElementShift(obj);
+
+ // Step 15.
+ var beginByteOffset = srcByteOffset + (beginIndex << elementShift);
+
+ // Steps 16-17.
+ return TypedArraySpeciesCreateWithBuffer(obj, buffer, beginByteOffset, newLength);
+}
+
+// ES6 draft rev30 (2014/12/24) 22.2.3.30 %TypedArray%.prototype.values()
+function TypedArrayValues() {
+ // Step 1.
+ var O = this;
+
+ // See the big comment in TypedArrayEntries for what we're doing here.
+ IsTypedArrayEnsuringArrayBuffer(O);
+
+ // Step 7.
+ return CreateArrayIterator(O, ITEM_KIND_VALUE);
+}
+_SetCanonicalName(TypedArrayValues, "values");
+
+// Proposed for ES7:
+// https://github.com/tc39/Array.prototype.includes/blob/7c023c19a0/spec.md
+function TypedArrayIncludes(searchElement, fromIndex = 0) {
+ // This function is not generic.
+ if (!IsObject(this) || !IsTypedArray(this)) {
+ return callFunction(CallTypedArrayMethodIfWrapped, this, searchElement,
+ fromIndex, "TypedArrayIncludes");
+ }
+
+ GetAttachedArrayBuffer(this);
+
+ // Steps 1-2.
+ var O = this;
+
+ // Steps 3-4.
+ var len = TypedArrayLength(O);
+
+ // Step 5.
+ if (len === 0)
+ return false;
+
+ // Steps 6-7.
+ var n = ToInteger(fromIndex);
+
+ var k;
+ // Step 8.
+ if (n >= 0) {
+ k = n;
+ }
+ // Step 9.
+ else {
+ // Step a.
+ k = len + n;
+ // Step b.
+ if (k < 0)
+ k = 0;
+ }
+
+ // Step 10.
+ while (k < len) {
+ // Steps a-c.
+ if (SameValueZero(searchElement, O[k]))
+ return true;
+
+ // Step d.
+ k++;
+ }
+
+ // Step 11.
+ return false;
+}
+
+// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
+// 22.2.2.1 %TypedArray%.from ( source [ , mapfn [ , thisArg ] ] )
+function TypedArrayStaticFrom(source, mapfn = undefined, thisArg = undefined) {
+ // Step 1.
+ var C = this;
+
+ // Step 2.
+ if (!IsConstructor(C))
+ ThrowTypeError(JSMSG_NOT_CONSTRUCTOR, typeof C);
+
+ // Step 3.
+ var mapping;
+ if (mapfn !== undefined) {
+ // Step 3.a.
+ if (!IsCallable(mapfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, mapfn));
+
+ // Step 3.b.
+ mapping = true;
+ } else {
+ // Step 4.
+ mapping = false;
+ }
+
+ // Step 5.
+ var T = thisArg;
+
+ // Step 6.
+ var usingIterator = GetMethod(source, std_iterator);
+
+ // Step 7.
+ if (usingIterator !== undefined) {
+ // Step 7.a.
+ // Inlined: 22.2.2.1.1 Runtime Semantics: IterableToList( items, method ).
+
+ // 22.2.2.1.1 IterableToList, step 1.
+ var iterator = GetIterator(source, usingIterator);
+
+ // 22.2.2.1.1 IterableToList, step 2.
+ var values = new List();
+
+ // 22.2.2.1.1 IterableToList, steps 3-4.
+ var i = 0;
+ while (true) {
+ // 22.2.2.1.1 IterableToList, step 4.a.
+ var next = callContentFunction(iterator.next, iterator);
+ if (!IsObject(next))
+ ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE);
+
+ // 22.2.2.1.1 IterableToList, step 4.b.
+ if (next.done)
+ break;
+ values[i++] = next.value;
+ }
+
+ // Step 7.b.
+ var len = i;
+
+ // Step 7.c.
+ var targetObj = TypedArrayCreateWithLength(C, len);
+
+ // Steps 7.d-e.
+ for (var k = 0; k < len; k++) {
+ // Step 7.e.ii.
+ var kValue = values[k];
+
+ // Steps 7.e.iii-iv.
+ var mappedValue = mapping ? callContentFunction(mapfn, T, kValue, k) : kValue;
+
+ // Step 7.e.v.
+ targetObj[k] = mappedValue;
+ }
+
+ // Step 7.f.
+ // Asserting that `values` is empty here would require removing them one by one from
+ // the list's start in the loop above. That would introduce unacceptable overhead.
+ // Additionally, the loop's logic is simple enough not to require the assert.
+
+ // Step 7.g.
+ return targetObj;
+ }
+
+ // Step 8 is an assertion: items is not an Iterator. Testing this is
+ // literally the very last thing we did, so we don't assert here.
+
+ // Step 9.
+ var arrayLike = ToObject(source);
+
+ // Step 10.
+ var len = ToLength(arrayLike.length);
+
+ // Step 11.
+ var targetObj = TypedArrayCreateWithLength(C, len);
+
+ // Steps 12-13.
+ for (var k = 0; k < len; k++) {
+ // Steps 13.a-b.
+ var kValue = arrayLike[k];
+
+ // Steps 13.c-d.
+ var mappedValue = mapping ? callContentFunction(mapfn, T, kValue, k) : kValue;
+
+ // Step 13.e.
+ targetObj[k] = mappedValue;
+ }
+
+ // Step 14.
+ return targetObj;
+}
+
+// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
+// 22.2.2.2 %TypedArray%.of ( ...items )
+function TypedArrayStaticOf(/*...items*/) {
+ // Step 1.
+ var len = arguments.length;
+
+ // Step 2.
+ var items = arguments;
+
+ // Step 3.
+ var C = this;
+
+ // Step 4.
+ if (!IsConstructor(C))
+ ThrowTypeError(JSMSG_NOT_CONSTRUCTOR, typeof C);
+
+ // Step 5.
+ var newObj = TypedArrayCreateWithLength(C, len);
+
+ // Steps 6-7.
+ for (var k = 0; k < len; k++)
+ newObj[k] = items[k]
+
+ // Step 8.
+ return newObj;
+}
+
+// ES 2016 draft Mar 25, 2016 22.2.2.4.
+function TypedArraySpecies() {
+ // Step 1.
+ return this;
+}
+_SetCanonicalName(TypedArraySpecies, "get [Symbol.species]");
+
+// ES 2017 draft June 2, 2016 22.2.3.32
+function TypedArrayToStringTag() {
+ // Step 1.
+ var O = this;
+
+ // Steps 2-3.
+ if (!IsObject(O) || !IsTypedArray(O))
+ return undefined;
+
+ // Steps 4-6.
+ // Modified to retrieve the [[TypedArrayName]] from the constructor.
+ return _NameForTypedArray(O);
+}
+_SetCanonicalName(TypedArrayToStringTag, "get [Symbol.toStringTag]");
+
+// ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
+// 22.2.2.1.1 Runtime Semantics: IterableToList( items, method )
+function IterableToList(items, method) {
+ // Step 1.
+ var iterator = GetIterator(items, method);
+
+ // Step 2.
+ var values = [];
+
+ // Steps 3-4.
+ var i = 0;
+ while (true) {
+ // Step 4.a.
+ var next = callContentFunction(iterator.next, iterator);
+ if (!IsObject(next))
+ ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE);
+
+ // Step 4.b.
+ if (next.done)
+ break;
+ _DefineDataProperty(values, i++, next.value);
+ }
+
+ // Step 5.
+ return values;
+}
+
+// ES 2016 draft Mar 25, 2016 24.1.4.3.
+function ArrayBufferSlice(start, end) {
+ // Step 1.
+ var O = this;
+
+ // Steps 2-3,
+ // This function is not generic.
+ if (!IsObject(O) || !IsArrayBuffer(O)) {
+ return callFunction(CallArrayBufferMethodIfWrapped, O, start, end,
+ "ArrayBufferSlice");
+ }
+
+ // Step 4.
+ if (IsDetachedBuffer(O))
+ ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
+
+ // Step 5.
+ var len = ArrayBufferByteLength(O);
+
+ // Step 6.
+ var relativeStart = ToInteger(start);
+
+ // Step 7.
+ var first = relativeStart < 0 ? std_Math_max(len + relativeStart, 0)
+ : std_Math_min(relativeStart, len);
+
+ // Step 8.
+ var relativeEnd = end === undefined ? len
+ : ToInteger(end);
+
+ // Step 9.
+ var final = relativeEnd < 0 ? std_Math_max(len + relativeEnd, 0)
+ : std_Math_min(relativeEnd, len);
+
+ // Step 10.
+ var newLen = std_Math_max(final - first, 0);
+
+ // Step 11
+ var ctor = SpeciesConstructor(O, GetBuiltinConstructor("ArrayBuffer"));
+
+ // Step 12.
+ var new_ = new ctor(newLen);
+
+ var isWrapped = false;
+ if (IsArrayBuffer(new_)) {
+ // Step 14.
+ if (IsDetachedBuffer(new_))
+ ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
+ } else {
+ // Step 13.
+ if (!IsWrappedArrayBuffer(new_))
+ ThrowTypeError(JSMSG_NON_ARRAY_BUFFER_RETURNED);
+
+ isWrapped = true;
+
+ // Step 14.
+ if (callFunction(CallArrayBufferMethodIfWrapped, new_, "IsDetachedBufferThis"))
+ ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
+ }
+
+ // Step 15.
+ if (new_ === O)
+ ThrowTypeError(JSMSG_SAME_ARRAY_BUFFER_RETURNED);
+
+ // Step 16.
+ var actualLen = PossiblyWrappedArrayBufferByteLength(new_);
+ if (actualLen < newLen)
+ ThrowTypeError(JSMSG_SHORT_ARRAY_BUFFER_RETURNED, newLen, actualLen);
+
+ // Step 18.
+ if (IsDetachedBuffer(O))
+ ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
+
+ // Steps 19-21.
+ ArrayBufferCopyData(new_, O, first | 0, newLen | 0, isWrapped);
+
+ // Step 22.
+ return new_;
+}
+
+function IsDetachedBufferThis() {
+ return IsDetachedBuffer(this);
+}
+
+// ES 2016 draft Mar 25, 2016 24.1.3.3.
+function ArrayBufferSpecies() {
+ // Step 1.
+ return this;
+}
+_SetCanonicalName(ArrayBufferSpecies, "get [Symbol.species]");
+
+// Shared memory and atomics proposal (30 Oct 2016)
+function SharedArrayBufferSpecies() {
+ // Step 1.
+ return this;
+}
+_SetCanonicalName(SharedArrayBufferSpecies, "get [Symbol.species]");
+
+// Shared memory and atomics proposal 6.2.1.5.3 (30 Oct 2016)
+// http://tc39.github.io/ecmascript_sharedmem/shmem.html
+function SharedArrayBufferSlice(start, end) {
+ // Step 1.
+ var O = this;
+
+ // Steps 2-4,
+ // This function is not generic.
+ if (!IsObject(O) || !IsSharedArrayBuffer(O)) {
+ return callFunction(CallSharedArrayBufferMethodIfWrapped, O, start, end,
+ "SharedArrayBufferSlice");
+ }
+
+ // Step 5.
+ var len = SharedArrayBufferByteLength(O);
+
+ // Step 6.
+ var relativeStart = ToInteger(start);
+
+ // Step 7.
+ var first = relativeStart < 0 ? std_Math_max(len + relativeStart, 0)
+ : std_Math_min(relativeStart, len);
+
+ // Step 8.
+ var relativeEnd = end === undefined ? len
+ : ToInteger(end);
+
+ // Step 9.
+ var final = relativeEnd < 0 ? std_Math_max(len + relativeEnd, 0)
+ : std_Math_min(relativeEnd, len);
+
+ // Step 10.
+ var newLen = std_Math_max(final - first, 0);
+
+ // Step 11
+ var ctor = SpeciesConstructor(O, GetBuiltinConstructor("SharedArrayBuffer"));
+
+ // Step 12.
+ var new_ = new ctor(newLen);
+
+ // Step 13.
+ var isWrapped = false;
+ if (!IsSharedArrayBuffer(new_)) {
+ if (!IsWrappedSharedArrayBuffer(new_))
+ ThrowTypeError(JSMSG_NON_SHARED_ARRAY_BUFFER_RETURNED);
+ isWrapped = true;
+ }
+
+ // Step 14.
+ if (new_ === O)
+ ThrowTypeError(JSMSG_SAME_SHARED_ARRAY_BUFFER_RETURNED);
+
+ // Step 15.
+ var actualLen = PossiblyWrappedSharedArrayBufferByteLength(new_);
+ if (actualLen < newLen)
+ ThrowTypeError(JSMSG_SHORT_SHARED_ARRAY_BUFFER_RETURNED, newLen, actualLen);
+
+ // Steps 16-18.
+ SharedArrayBufferCopyData(new_, O, first | 0, newLen | 0, isWrapped);
+
+ // Step 19.
+ return new_;
+}
diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp
new file mode 100644
index 000000000..b7297c894
--- /dev/null
+++ b/js/src/builtin/TypedObject.cpp
@@ -0,0 +1,3008 @@
+/* -*- 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 "builtin/TypedObject.h"
+
+#include "mozilla/Casting.h"
+#include "mozilla/CheckedInt.h"
+
+#include "jscompartment.h"
+#include "jsfun.h"
+#include "jsutil.h"
+
+#include "builtin/SIMD.h"
+#include "gc/Marking.h"
+#include "js/Vector.h"
+#include "vm/GlobalObject.h"
+#include "vm/String.h"
+#include "vm/StringBuffer.h"
+#include "vm/TypedArrayObject.h"
+
+#include "jsatominlines.h"
+#include "jsobjinlines.h"
+
+#include "gc/StoreBuffer-inl.h"
+#include "vm/NativeObject-inl.h"
+#include "vm/Shape-inl.h"
+
+using mozilla::AssertedCast;
+using mozilla::CheckedInt32;
+using mozilla::DebugOnly;
+using mozilla::IsPowerOfTwo;
+using mozilla::PodCopy;
+using mozilla::PointerRangeSize;
+
+using namespace js;
+
+const Class js::TypedObjectModuleObject::class_ = {
+ "TypedObject",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_TypedObject)
+};
+
+static const JSFunctionSpec TypedObjectMethods[] = {
+ JS_SELF_HOSTED_FN("objectType", "TypeOfTypedObject", 1, 0),
+ JS_SELF_HOSTED_FN("storage", "StorageOfTypedObject", 1, 0),
+ JS_FS_END
+};
+
+static void
+ReportCannotConvertTo(JSContext* cx, HandleValue fromValue, const char* toType)
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
+ InformalValueTypeName(fromValue), toType);
+}
+
+template<class T>
+static inline T*
+ToObjectIf(HandleValue value)
+{
+ if (!value.isObject())
+ return nullptr;
+
+ if (!value.toObject().is<T>())
+ return nullptr;
+
+ return &value.toObject().as<T>();
+}
+
+static inline CheckedInt32
+RoundUpToAlignment(CheckedInt32 address, uint32_t align)
+{
+ MOZ_ASSERT(IsPowerOfTwo(align));
+
+ // Note: Be careful to order operators such that we first make the
+ // value smaller and then larger, so that we don't get false
+ // overflow errors due to (e.g.) adding `align` and then
+ // subtracting `1` afterwards when merely adding `align-1` would
+ // not have overflowed. Note that due to the nature of two's
+ // complement representation, if `address` is already aligned,
+ // then adding `align-1` cannot itself cause an overflow.
+
+ return ((address + (align - 1)) / align) * align;
+}
+
+/*
+ * Overwrites the contents of `typedObj` at offset `offset` with `val`
+ * converted to the type `typeObj`. This is done by delegating to
+ * self-hosted code. This is used for assignments and initializations.
+ *
+ * For example, consider the final assignment in this snippet:
+ *
+ * var Point = new StructType({x: float32, y: float32});
+ * var Line = new StructType({from: Point, to: Point});
+ * var line = new Line();
+ * line.to = {x: 22, y: 44};
+ *
+ * This would result in a call to `ConvertAndCopyTo`
+ * where:
+ * - typeObj = Point
+ * - typedObj = line
+ * - offset = sizeof(Point) == 8
+ * - val = {x: 22, y: 44}
+ * This would result in loading the value of `x`, converting
+ * it to a float32, and hen storing it at the appropriate offset,
+ * and then doing the same for `y`.
+ *
+ * Note that the type of `typeObj` may not be the
+ * type of `typedObj` but rather some subcomponent of `typedObj`.
+ */
+static bool
+ConvertAndCopyTo(JSContext* cx,
+ HandleTypeDescr typeObj,
+ HandleTypedObject typedObj,
+ int32_t offset,
+ HandleAtom name,
+ HandleValue val)
+{
+ RootedFunction func(cx, SelfHostedFunction(cx, cx->names().ConvertAndCopyTo));
+ if (!func)
+ return false;
+
+ FixedInvokeArgs<5> args(cx);
+
+ args[0].setObject(*typeObj);
+ args[1].setObject(*typedObj);
+ args[2].setInt32(offset);
+ if (name)
+ args[3].setString(name);
+ else
+ args[3].setNull();
+ args[4].set(val);
+
+ RootedValue fval(cx, ObjectValue(*func));
+ RootedValue dummy(cx); // ignored by ConvertAndCopyTo
+ return js::Call(cx, fval, dummy, args, &dummy);
+}
+
+static bool
+ConvertAndCopyTo(JSContext* cx, HandleTypedObject typedObj, HandleValue val)
+{
+ Rooted<TypeDescr*> type(cx, &typedObj->typeDescr());
+ return ConvertAndCopyTo(cx, type, typedObj, 0, nullptr, val);
+}
+
+/*
+ * Overwrites the contents of `typedObj` at offset `offset` with `val`
+ * converted to the type `typeObj`
+ */
+static bool
+Reify(JSContext* cx,
+ HandleTypeDescr type,
+ HandleTypedObject typedObj,
+ size_t offset,
+ MutableHandleValue to)
+{
+ RootedFunction func(cx, SelfHostedFunction(cx, cx->names().Reify));
+ if (!func)
+ return false;
+
+ FixedInvokeArgs<3> args(cx);
+
+ args[0].setObject(*type);
+ args[1].setObject(*typedObj);
+ args[2].setInt32(offset);
+
+ RootedValue fval(cx, ObjectValue(*func));
+ return js::Call(cx, fval, UndefinedHandleValue, args, to);
+}
+
+// Extracts the `prototype` property from `obj`, throwing if it is
+// missing or not an object.
+static JSObject*
+GetPrototype(JSContext* cx, HandleObject obj)
+{
+ RootedValue prototypeVal(cx);
+ if (!GetProperty(cx, obj, obj, cx->names().prototype,
+ &prototypeVal))
+ {
+ return nullptr;
+ }
+ if (!prototypeVal.isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_PROTOTYPE);
+ return nullptr;
+ }
+ return &prototypeVal.toObject();
+}
+
+/***************************************************************************
+ * Typed Prototypes
+ *
+ * Every type descriptor has an associated prototype. Instances of
+ * that type descriptor use this as their prototype. Per the spec,
+ * typed object prototypes cannot be mutated.
+ */
+
+const Class js::TypedProto::class_ = {
+ "TypedProto",
+ JSCLASS_HAS_RESERVED_SLOTS(JS_TYPROTO_SLOTS)
+};
+
+/***************************************************************************
+ * Scalar type objects
+ *
+ * Scalar type objects like `uint8`, `uint16`, are all instances of
+ * the ScalarTypeDescr class. Like all type objects, they have a reserved
+ * slot pointing to a TypeRepresentation object, which is used to
+ * distinguish which scalar type object this actually is.
+ */
+
+static const ClassOps ScalarTypeDescrClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ TypeDescr::finalize,
+ ScalarTypeDescr::call
+};
+
+const Class js::ScalarTypeDescr::class_ = {
+ "Scalar",
+ JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
+ &ScalarTypeDescrClassOps
+};
+
+const JSFunctionSpec js::ScalarTypeDescr::typeObjectMethods[] = {
+ JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
+ JS_SELF_HOSTED_FN("array", "ArrayShorthand", 1, JSFUN_HAS_REST),
+ JS_SELF_HOSTED_FN("equivalent", "TypeDescrEquivalent", 1, 0),
+ JS_FS_END
+};
+
+uint32_t
+ScalarTypeDescr::size(Type t)
+{
+ return AssertedCast<uint32_t>(Scalar::byteSize(t));
+}
+
+uint32_t
+ScalarTypeDescr::alignment(Type t)
+{
+ return AssertedCast<uint32_t>(Scalar::byteSize(t));
+}
+
+/*static*/ const char*
+ScalarTypeDescr::typeName(Type type)
+{
+ switch (type) {
+#define NUMERIC_TYPE_TO_STRING(constant_, type_, name_) \
+ case constant_: return #name_;
+ JS_FOR_EACH_SCALAR_TYPE_REPR(NUMERIC_TYPE_TO_STRING)
+#undef NUMERIC_TYPE_TO_STRING
+ case Scalar::Int64:
+ case Scalar::Float32x4:
+ case Scalar::Int8x16:
+ case Scalar::Int16x8:
+ case Scalar::Int32x4:
+ case Scalar::MaxTypedArrayViewType:
+ break;
+ }
+ MOZ_CRASH("Invalid type");
+}
+
+bool
+ScalarTypeDescr::call(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() < 1) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+ args.callee().getClass()->name, "0", "s");
+ return false;
+ }
+
+ Rooted<ScalarTypeDescr*> descr(cx, &args.callee().as<ScalarTypeDescr>());
+ ScalarTypeDescr::Type type = descr->type();
+
+ double number;
+ if (!ToNumber(cx, args[0], &number))
+ return false;
+
+ if (type == Scalar::Uint8Clamped)
+ number = ClampDoubleToUint8(number);
+
+ switch (type) {
+#define SCALARTYPE_CALL(constant_, type_, name_) \
+ case constant_: { \
+ type_ converted = ConvertScalar<type_>(number); \
+ args.rval().setNumber((double) converted); \
+ return true; \
+ }
+
+ JS_FOR_EACH_SCALAR_TYPE_REPR(SCALARTYPE_CALL)
+#undef SCALARTYPE_CALL
+ case Scalar::Int64:
+ case Scalar::Float32x4:
+ case Scalar::Int8x16:
+ case Scalar::Int16x8:
+ case Scalar::Int32x4:
+ case Scalar::MaxTypedArrayViewType:
+ MOZ_CRASH();
+ }
+ return true;
+}
+
+/***************************************************************************
+ * Reference type objects
+ *
+ * Reference type objects like `Any` or `Object` basically work the
+ * same way that the scalar type objects do. There is one class with
+ * many instances, and each instance has a reserved slot with a
+ * TypeRepresentation object, which is used to distinguish which
+ * reference type object this actually is.
+ */
+
+static const ClassOps ReferenceTypeDescrClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ TypeDescr::finalize,
+ ReferenceTypeDescr::call
+};
+
+const Class js::ReferenceTypeDescr::class_ = {
+ "Reference",
+ JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
+ &ReferenceTypeDescrClassOps
+};
+
+const JSFunctionSpec js::ReferenceTypeDescr::typeObjectMethods[] = {
+ JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
+ {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
+ {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"},
+ JS_FS_END
+};
+
+static const uint32_t ReferenceSizes[] = {
+#define REFERENCE_SIZE(_kind, _type, _name) \
+ sizeof(_type),
+ JS_FOR_EACH_REFERENCE_TYPE_REPR(REFERENCE_SIZE) 0
+#undef REFERENCE_SIZE
+};
+
+uint32_t
+ReferenceTypeDescr::size(Type t)
+{
+ return ReferenceSizes[t];
+}
+
+uint32_t
+ReferenceTypeDescr::alignment(Type t)
+{
+ return ReferenceSizes[t];
+}
+
+/*static*/ const char*
+ReferenceTypeDescr::typeName(Type type)
+{
+ switch (type) {
+#define NUMERIC_TYPE_TO_STRING(constant_, type_, name_) \
+ case constant_: return #name_;
+ JS_FOR_EACH_REFERENCE_TYPE_REPR(NUMERIC_TYPE_TO_STRING)
+#undef NUMERIC_TYPE_TO_STRING
+ }
+ MOZ_CRASH("Invalid type");
+}
+
+bool
+js::ReferenceTypeDescr::call(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ MOZ_ASSERT(args.callee().is<ReferenceTypeDescr>());
+ Rooted<ReferenceTypeDescr*> descr(cx, &args.callee().as<ReferenceTypeDescr>());
+
+ if (args.length() < 1) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+ descr->typeName(), "0", "s");
+ return false;
+ }
+
+ switch (descr->type()) {
+ case ReferenceTypeDescr::TYPE_ANY:
+ args.rval().set(args[0]);
+ return true;
+
+ case ReferenceTypeDescr::TYPE_OBJECT:
+ {
+ RootedObject obj(cx, ToObject(cx, args[0]));
+ if (!obj)
+ return false;
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ case ReferenceTypeDescr::TYPE_STRING:
+ {
+ RootedString obj(cx, ToString<CanGC>(cx, args[0]));
+ if (!obj)
+ return false;
+ args.rval().setString(&*obj);
+ return true;
+ }
+ }
+
+ MOZ_CRASH("Unhandled Reference type");
+}
+
+/***************************************************************************
+ * SIMD type objects
+ *
+ * Note: these are partially defined in SIMD.cpp
+ */
+
+SimdType
+SimdTypeDescr::type() const {
+ uint32_t t = uint32_t(getReservedSlot(JS_DESCR_SLOT_TYPE).toInt32());
+ MOZ_ASSERT(t < uint32_t(SimdType::Count));
+ return SimdType(t);
+}
+
+uint32_t
+SimdTypeDescr::size(SimdType t)
+{
+ MOZ_ASSERT(unsigned(t) < unsigned(SimdType::Count));
+ switch (t) {
+ case SimdType::Int8x16:
+ case SimdType::Int16x8:
+ case SimdType::Int32x4:
+ case SimdType::Uint8x16:
+ case SimdType::Uint16x8:
+ case SimdType::Uint32x4:
+ case SimdType::Float32x4:
+ case SimdType::Float64x2:
+ case SimdType::Bool8x16:
+ case SimdType::Bool16x8:
+ case SimdType::Bool32x4:
+ case SimdType::Bool64x2:
+ return 16;
+ case SimdType::Count:
+ break;
+ }
+ MOZ_CRASH("unexpected SIMD type");
+}
+
+uint32_t
+SimdTypeDescr::alignment(SimdType t)
+{
+ MOZ_ASSERT(unsigned(t) < unsigned(SimdType::Count));
+ return size(t);
+}
+
+/***************************************************************************
+ * ArrayMetaTypeDescr class
+ */
+
+/*
+ * For code like:
+ *
+ * var A = new TypedObject.ArrayType(uint8, 10);
+ * var S = new TypedObject.StructType({...});
+ *
+ * As usual, the [[Prototype]] of A is
+ * TypedObject.ArrayType.prototype. This permits adding methods to
+ * all ArrayType types, by setting
+ * TypedObject.ArrayType.prototype.methodName = function() { ... }.
+ * The same holds for S with respect to TypedObject.StructType.
+ *
+ * We may also want to add methods to *instances* of an ArrayType:
+ *
+ * var a = new A();
+ * var s = new S();
+ *
+ * As usual, the [[Prototype]] of a is A.prototype. What's
+ * A.prototype? It's an empty object, and you can set
+ * A.prototype.methodName = function() { ... } to add a method to all
+ * A instances. (And the same with respect to s and S.)
+ *
+ * But what if you want to add a method to all ArrayType instances,
+ * not just all A instances? (Or to all StructType instances.) The
+ * [[Prototype]] of the A.prototype empty object is
+ * TypedObject.ArrayType.prototype.prototype (two .prototype levels!).
+ * So just set TypedObject.ArrayType.prototype.prototype.methodName =
+ * function() { ... } to add a method to all ArrayType instances.
+ * (And, again, same with respect to s and S.)
+ *
+ * This function creates the A.prototype/S.prototype object. It returns an
+ * empty object with the .prototype.prototype object as its [[Prototype]].
+ */
+static TypedProto*
+CreatePrototypeObjectForComplexTypeInstance(JSContext* cx, HandleObject ctorPrototype)
+{
+ RootedObject ctorPrototypePrototype(cx, GetPrototype(cx, ctorPrototype));
+ if (!ctorPrototypePrototype)
+ return nullptr;
+
+ return NewObjectWithGivenProto<TypedProto>(cx, ctorPrototypePrototype, SingletonObject);
+}
+
+static const ClassOps ArrayTypeDescrClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ TypeDescr::finalize,
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ TypedObject::construct
+};
+
+const Class ArrayTypeDescr::class_ = {
+ "ArrayType",
+ JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
+ &ArrayTypeDescrClassOps
+};
+
+const JSPropertySpec ArrayMetaTypeDescr::typeObjectProperties[] = {
+ JS_PS_END
+};
+
+const JSFunctionSpec ArrayMetaTypeDescr::typeObjectMethods[] = {
+ {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
+ JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
+ {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"},
+ JS_SELF_HOSTED_FN("build", "TypedObjectArrayTypeBuild", 3, 0),
+ JS_SELF_HOSTED_FN("from", "TypedObjectArrayTypeFrom", 3, 0),
+ JS_FS_END
+};
+
+const JSPropertySpec ArrayMetaTypeDescr::typedObjectProperties[] = {
+ JS_PS_END
+};
+
+const JSFunctionSpec ArrayMetaTypeDescr::typedObjectMethods[] = {
+ {"forEach", {nullptr, nullptr}, 1, 0, "ArrayForEach"},
+ {"redimension", {nullptr, nullptr}, 1, 0, "TypedObjectArrayRedimension"},
+ JS_SELF_HOSTED_FN("map", "TypedObjectArrayMap", 2, 0),
+ JS_SELF_HOSTED_FN("reduce", "TypedObjectArrayReduce", 2, 0),
+ JS_SELF_HOSTED_FN("filter", "TypedObjectArrayFilter", 1, 0),
+ JS_FS_END
+};
+
+bool
+js::CreateUserSizeAndAlignmentProperties(JSContext* cx, HandleTypeDescr descr)
+{
+ // If data is transparent, also store the public slots.
+ if (descr->transparent()) {
+ // byteLength
+ RootedValue typeByteLength(cx, Int32Value(AssertedCast<int32_t>(descr->size())));
+ if (!DefineProperty(cx, descr, cx->names().byteLength, typeByteLength,
+ nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+ {
+ return false;
+ }
+
+ // byteAlignment
+ RootedValue typeByteAlignment(cx, Int32Value(descr->alignment()));
+ if (!DefineProperty(cx, descr, cx->names().byteAlignment, typeByteAlignment,
+ nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+ {
+ return false;
+ }
+ } else {
+ // byteLength
+ if (!DefineProperty(cx, descr, cx->names().byteLength, UndefinedHandleValue,
+ nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+ {
+ return false;
+ }
+
+ // byteAlignment
+ if (!DefineProperty(cx, descr, cx->names().byteAlignment, UndefinedHandleValue,
+ nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool
+CreateTraceList(JSContext* cx, HandleTypeDescr descr);
+
+ArrayTypeDescr*
+ArrayMetaTypeDescr::create(JSContext* cx,
+ HandleObject arrayTypePrototype,
+ HandleTypeDescr elementType,
+ HandleAtom stringRepr,
+ int32_t size,
+ int32_t length)
+{
+ MOZ_ASSERT(arrayTypePrototype);
+ Rooted<ArrayTypeDescr*> obj(cx);
+ obj = NewObjectWithGivenProto<ArrayTypeDescr>(cx, arrayTypePrototype, SingletonObject);
+ if (!obj)
+ return nullptr;
+
+ obj->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(ArrayTypeDescr::Kind));
+ obj->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(stringRepr));
+ obj->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(elementType->alignment()));
+ obj->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(size));
+ obj->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(elementType->opaque()));
+ obj->initReservedSlot(JS_DESCR_SLOT_ARRAY_ELEM_TYPE, ObjectValue(*elementType));
+ obj->initReservedSlot(JS_DESCR_SLOT_ARRAY_LENGTH, Int32Value(length));
+
+ RootedValue elementTypeVal(cx, ObjectValue(*elementType));
+ if (!DefineProperty(cx, obj, cx->names().elementType, elementTypeVal,
+ nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+ {
+ return nullptr;
+ }
+
+ RootedValue lengthValue(cx, NumberValue(length));
+ if (!DefineProperty(cx, obj, cx->names().length, lengthValue,
+ nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+ {
+ return nullptr;
+ }
+
+ if (!CreateUserSizeAndAlignmentProperties(cx, obj))
+ return nullptr;
+
+ // All arrays with the same element type have the same prototype. This
+ // prototype is created lazily and stored in the element type descriptor.
+ Rooted<TypedProto*> prototypeObj(cx);
+ if (elementType->getReservedSlot(JS_DESCR_SLOT_ARRAYPROTO).isObject()) {
+ prototypeObj = &elementType->getReservedSlot(JS_DESCR_SLOT_ARRAYPROTO).toObject().as<TypedProto>();
+ } else {
+ prototypeObj = CreatePrototypeObjectForComplexTypeInstance(cx, arrayTypePrototype);
+ if (!prototypeObj)
+ return nullptr;
+ elementType->setReservedSlot(JS_DESCR_SLOT_ARRAYPROTO, ObjectValue(*prototypeObj));
+ }
+
+ obj->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*prototypeObj));
+
+ if (!LinkConstructorAndPrototype(cx, obj, prototypeObj))
+ return nullptr;
+
+ if (!CreateTraceList(cx, obj))
+ return nullptr;
+
+ if (!cx->zone()->typeDescrObjects.put(obj)) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ return obj;
+}
+
+bool
+ArrayMetaTypeDescr::construct(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "ArrayType"))
+ return false;
+
+ RootedObject arrayTypeGlobal(cx, &args.callee());
+
+ // Expect two arguments. The first is a type object, the second is a length.
+ if (args.length() < 2) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+ "ArrayType", "1", "");
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<TypeDescr>()) {
+ ReportCannotConvertTo(cx, args[0], "ArrayType element specifier");
+ return false;
+ }
+
+ if (!args[1].isInt32() || args[1].toInt32() < 0) {
+ ReportCannotConvertTo(cx, args[1], "ArrayType length specifier");
+ return false;
+ }
+
+ Rooted<TypeDescr*> elementType(cx, &args[0].toObject().as<TypeDescr>());
+
+ int32_t length = args[1].toInt32();
+
+ // Compute the byte size.
+ CheckedInt32 size = CheckedInt32(elementType->size()) * length;
+ if (!size.isValid()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_TOO_BIG);
+ return false;
+ }
+
+ // Construct a canonical string `new ArrayType(<elementType>, N)`:
+ StringBuffer contents(cx);
+ if (!contents.append("new ArrayType("))
+ return false;
+ if (!contents.append(&elementType->stringRepr()))
+ return false;
+ if (!contents.append(", "))
+ return false;
+ if (!NumberValueToStringBuffer(cx, NumberValue(length), contents))
+ return false;
+ if (!contents.append(")"))
+ return false;
+ RootedAtom stringRepr(cx, contents.finishAtom());
+ if (!stringRepr)
+ return false;
+
+ // Extract ArrayType.prototype
+ RootedObject arrayTypePrototype(cx, GetPrototype(cx, arrayTypeGlobal));
+ if (!arrayTypePrototype)
+ return false;
+
+ // Create the instance of ArrayType
+ Rooted<ArrayTypeDescr*> obj(cx);
+ obj = create(cx, arrayTypePrototype, elementType, stringRepr, size.value(), length);
+ if (!obj)
+ return false;
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+bool
+js::IsTypedObjectArray(JSObject& obj)
+{
+ if (!obj.is<TypedObject>())
+ return false;
+ TypeDescr& d = obj.as<TypedObject>().typeDescr();
+ return d.is<ArrayTypeDescr>();
+}
+
+/*********************************
+ * StructType class
+ */
+
+static const ClassOps StructTypeDescrClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ TypeDescr::finalize,
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ TypedObject::construct
+};
+
+const Class StructTypeDescr::class_ = {
+ "StructType",
+ JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
+ &StructTypeDescrClassOps
+};
+
+const JSPropertySpec StructMetaTypeDescr::typeObjectProperties[] = {
+ JS_PS_END
+};
+
+const JSFunctionSpec StructMetaTypeDescr::typeObjectMethods[] = {
+ {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
+ JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
+ {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"},
+ JS_FS_END
+};
+
+const JSPropertySpec StructMetaTypeDescr::typedObjectProperties[] = {
+ JS_PS_END
+};
+
+const JSFunctionSpec StructMetaTypeDescr::typedObjectMethods[] = {
+ JS_FS_END
+};
+
+JSObject*
+StructMetaTypeDescr::create(JSContext* cx,
+ HandleObject metaTypeDescr,
+ HandleObject fields)
+{
+ // Obtain names of fields, which are the own properties of `fields`
+ AutoIdVector ids(cx);
+ if (!GetPropertyKeys(cx, fields, JSITER_OWNONLY | JSITER_SYMBOLS, &ids))
+ return nullptr;
+
+ // Iterate through each field. Collect values for the various
+ // vectors below and also track total size and alignment. Be wary
+ // of overflow!
+ StringBuffer stringBuffer(cx); // Canonical string repr
+ AutoValueVector fieldNames(cx); // Name of each field.
+ AutoValueVector fieldTypeObjs(cx); // Type descriptor of each field.
+ AutoValueVector fieldOffsets(cx); // Offset of each field field.
+ RootedObject userFieldOffsets(cx); // User-exposed {f:offset} object
+ RootedObject userFieldTypes(cx); // User-exposed {f:descr} object.
+ CheckedInt32 sizeSoFar(0); // Size of struct thus far.
+ uint32_t alignment = 1; // Alignment of struct.
+ bool opaque = false; // Opacity of struct.
+
+ userFieldOffsets = NewBuiltinClassInstance<PlainObject>(cx, TenuredObject);
+ if (!userFieldOffsets)
+ return nullptr;
+
+ userFieldTypes = NewBuiltinClassInstance<PlainObject>(cx, TenuredObject);
+ if (!userFieldTypes)
+ return nullptr;
+
+ if (!stringBuffer.append("new StructType({"))
+ return nullptr;
+
+ RootedValue fieldTypeVal(cx);
+ RootedId id(cx);
+ Rooted<TypeDescr*> fieldType(cx);
+ for (unsigned int i = 0; i < ids.length(); i++) {
+ id = ids[i];
+
+ // Check that all the property names are non-numeric strings.
+ uint32_t unused;
+ if (!JSID_IS_ATOM(id) || JSID_TO_ATOM(id)->isIndex(&unused)) {
+ RootedValue idValue(cx, IdToValue(id));
+ ReportCannotConvertTo(cx, idValue, "StructType field name");
+ return nullptr;
+ }
+
+ // Load the value for the current field from the `fields` object.
+ // The value should be a type descriptor.
+ if (!GetProperty(cx, fields, fields, id, &fieldTypeVal))
+ return nullptr;
+ fieldType = ToObjectIf<TypeDescr>(fieldTypeVal);
+ if (!fieldType) {
+ ReportCannotConvertTo(cx, fieldTypeVal, "StructType field specifier");
+ return nullptr;
+ }
+
+ // Collect field name and type object
+ RootedValue fieldName(cx, IdToValue(id));
+ if (!fieldNames.append(fieldName))
+ return nullptr;
+ if (!fieldTypeObjs.append(ObjectValue(*fieldType)))
+ return nullptr;
+
+ // userFieldTypes[id] = typeObj
+ if (!DefineProperty(cx, userFieldTypes, id, fieldTypeObjs[i], nullptr, nullptr,
+ JSPROP_READONLY | JSPROP_PERMANENT))
+ {
+ return nullptr;
+ }
+
+ // Append "f:Type" to the string repr
+ if (i > 0 && !stringBuffer.append(", "))
+ return nullptr;
+ if (!stringBuffer.append(JSID_TO_ATOM(id)))
+ return nullptr;
+ if (!stringBuffer.append(": "))
+ return nullptr;
+ if (!stringBuffer.append(&fieldType->stringRepr()))
+ return nullptr;
+
+ // Offset of this field is the current total size adjusted for
+ // the field's alignment.
+ CheckedInt32 offset = RoundUpToAlignment(sizeSoFar, fieldType->alignment());
+ if (!offset.isValid()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_TOO_BIG);
+ return nullptr;
+ }
+ MOZ_ASSERT(offset.value() >= 0);
+ if (!fieldOffsets.append(Int32Value(offset.value())))
+ return nullptr;
+
+ // userFieldOffsets[id] = offset
+ RootedValue offsetValue(cx, Int32Value(offset.value()));
+ if (!DefineProperty(cx, userFieldOffsets, id, offsetValue, nullptr, nullptr,
+ JSPROP_READONLY | JSPROP_PERMANENT))
+ {
+ return nullptr;
+ }
+
+ // Add space for this field to the total struct size.
+ sizeSoFar = offset + fieldType->size();
+ if (!sizeSoFar.isValid()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_TOO_BIG);
+ return nullptr;
+ }
+
+ // Struct is opaque if any field is opaque
+ if (fieldType->opaque())
+ opaque = true;
+
+ // Alignment of the struct is the max of the alignment of its fields.
+ alignment = js::Max(alignment, fieldType->alignment());
+ }
+
+ // Complete string representation.
+ if (!stringBuffer.append("})"))
+ return nullptr;
+
+ RootedAtom stringRepr(cx, stringBuffer.finishAtom());
+ if (!stringRepr)
+ return nullptr;
+
+ // Adjust the total size to be a multiple of the final alignment.
+ CheckedInt32 totalSize = RoundUpToAlignment(sizeSoFar, alignment);
+ if (!totalSize.isValid()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_TOO_BIG);
+ return nullptr;
+ }
+
+ // Now create the resulting type descriptor.
+ RootedObject structTypePrototype(cx, GetPrototype(cx, metaTypeDescr));
+ if (!structTypePrototype)
+ return nullptr;
+
+ Rooted<StructTypeDescr*> descr(cx);
+ descr = NewObjectWithGivenProto<StructTypeDescr>(cx, structTypePrototype, SingletonObject);
+ if (!descr)
+ return nullptr;
+
+ descr->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(type::Struct));
+ descr->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(stringRepr));
+ descr->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(AssertedCast<int32_t>(alignment)));
+ descr->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(totalSize.value()));
+ descr->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(opaque));
+
+ // Construct for internal use an array with the name for each field.
+ {
+ RootedObject fieldNamesVec(cx);
+ fieldNamesVec = NewDenseCopiedArray(cx, fieldNames.length(),
+ fieldNames.begin(), nullptr,
+ TenuredObject);
+ if (!fieldNamesVec)
+ return nullptr;
+ descr->initReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_NAMES, ObjectValue(*fieldNamesVec));
+ }
+
+ // Construct for internal use an array with the type object for each field.
+ RootedObject fieldTypeVec(cx);
+ fieldTypeVec = NewDenseCopiedArray(cx, fieldTypeObjs.length(),
+ fieldTypeObjs.begin(), nullptr,
+ TenuredObject);
+ if (!fieldTypeVec)
+ return nullptr;
+ descr->initReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_TYPES, ObjectValue(*fieldTypeVec));
+
+ // Construct for internal use an array with the offset for each field.
+ {
+ RootedObject fieldOffsetsVec(cx);
+ fieldOffsetsVec = NewDenseCopiedArray(cx, fieldOffsets.length(),
+ fieldOffsets.begin(), nullptr,
+ TenuredObject);
+ if (!fieldOffsetsVec)
+ return nullptr;
+ descr->initReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_OFFSETS, ObjectValue(*fieldOffsetsVec));
+ }
+
+ // Create data properties fieldOffsets and fieldTypes
+ if (!FreezeObject(cx, userFieldOffsets))
+ return nullptr;
+ if (!FreezeObject(cx, userFieldTypes))
+ return nullptr;
+ RootedValue userFieldOffsetsValue(cx, ObjectValue(*userFieldOffsets));
+ if (!DefineProperty(cx, descr, cx->names().fieldOffsets, userFieldOffsetsValue,
+ nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+ {
+ return nullptr;
+ }
+ RootedValue userFieldTypesValue(cx, ObjectValue(*userFieldTypes));
+ if (!DefineProperty(cx, descr, cx->names().fieldTypes, userFieldTypesValue,
+ nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+ {
+ return nullptr;
+ }
+
+ if (!CreateUserSizeAndAlignmentProperties(cx, descr))
+ return nullptr;
+
+ Rooted<TypedProto*> prototypeObj(cx);
+ prototypeObj = CreatePrototypeObjectForComplexTypeInstance(cx, structTypePrototype);
+ if (!prototypeObj)
+ return nullptr;
+
+ descr->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*prototypeObj));
+
+ if (!LinkConstructorAndPrototype(cx, descr, prototypeObj))
+ return nullptr;
+
+ if (!CreateTraceList(cx, descr))
+ return nullptr;
+
+ if (!cx->zone()->typeDescrObjects.put(descr) ||
+ !cx->zone()->typeDescrObjects.put(fieldTypeVec))
+ {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ return descr;
+}
+
+bool
+StructMetaTypeDescr::construct(JSContext* cx, unsigned int argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "StructType"))
+ return false;
+
+ if (args.length() >= 1 && args[0].isObject()) {
+ RootedObject metaTypeDescr(cx, &args.callee());
+ RootedObject fields(cx, &args[0].toObject());
+ RootedObject obj(cx, create(cx, metaTypeDescr, fields));
+ if (!obj)
+ return false;
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_ARGS);
+ return false;
+}
+
+size_t
+StructTypeDescr::fieldCount() const
+{
+ return fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_NAMES).getDenseInitializedLength();
+}
+
+bool
+StructTypeDescr::fieldIndex(jsid id, size_t* out) const
+{
+ ArrayObject& fieldNames = fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_NAMES);
+ size_t l = fieldNames.getDenseInitializedLength();
+ for (size_t i = 0; i < l; i++) {
+ JSAtom& a = fieldNames.getDenseElement(i).toString()->asAtom();
+ if (JSID_IS_ATOM(id, &a)) {
+ *out = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+JSAtom&
+StructTypeDescr::fieldName(size_t index) const
+{
+ return fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_NAMES).getDenseElement(index).toString()->asAtom();
+}
+
+size_t
+StructTypeDescr::fieldOffset(size_t index) const
+{
+ ArrayObject& fieldOffsets = fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_OFFSETS);
+ MOZ_ASSERT(index < fieldOffsets.getDenseInitializedLength());
+ return AssertedCast<size_t>(fieldOffsets.getDenseElement(index).toInt32());
+}
+
+TypeDescr&
+StructTypeDescr::fieldDescr(size_t index) const
+{
+ ArrayObject& fieldDescrs = fieldInfoObject(JS_DESCR_SLOT_STRUCT_FIELD_TYPES);
+ MOZ_ASSERT(index < fieldDescrs.getDenseInitializedLength());
+ return fieldDescrs.getDenseElement(index).toObject().as<TypeDescr>();
+}
+
+/******************************************************************************
+ * Creating the TypedObject "module"
+ *
+ * We create one global, `TypedObject`, which contains the following
+ * members:
+ *
+ * 1. uint8, uint16, etc
+ * 2. ArrayType
+ * 3. StructType
+ *
+ * Each of these is a function and hence their prototype is
+ * `Function.__proto__` (in terms of the JS Engine, they are not
+ * JSFunctions but rather instances of their own respective JSClasses
+ * which override the call and construct operations).
+ *
+ * Each type object also has its own `prototype` field. Therefore,
+ * using `StructType` as an example, the basic setup is:
+ *
+ * StructType --__proto__--> Function.__proto__
+ * |
+ * prototype -- prototype --> { }
+ * |
+ * v
+ * { } -----__proto__--> Function.__proto__
+ *
+ * When a new type object (e.g., an instance of StructType) is created,
+ * it will look as follows:
+ *
+ * MyStruct -__proto__-> StructType.prototype -__proto__-> Function.__proto__
+ * | |
+ * | prototype
+ * | |
+ * | v
+ * prototype -----__proto__----> { }
+ * |
+ * v
+ * { } --__proto__-> Object.prototype
+ *
+ * Finally, when an instance of `MyStruct` is created, its
+ * structure is as follows:
+ *
+ * object -__proto__->
+ * MyStruct.prototype -__proto__->
+ * StructType.prototype.prototype -__proto__->
+ * Object.prototype
+ */
+
+// Here `T` is either `ScalarTypeDescr` or `ReferenceTypeDescr`
+template<typename T>
+static bool
+DefineSimpleTypeDescr(JSContext* cx,
+ Handle<GlobalObject*> global,
+ HandleObject module,
+ typename T::Type type,
+ HandlePropertyName className)
+{
+ RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
+ if (!objProto)
+ return false;
+
+ RootedObject funcProto(cx, global->getOrCreateFunctionPrototype(cx));
+ if (!funcProto)
+ return false;
+
+ Rooted<T*> descr(cx);
+ descr = NewObjectWithGivenProto<T>(cx, funcProto, SingletonObject);
+ if (!descr)
+ return false;
+
+ descr->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(T::Kind));
+ descr->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(className));
+ descr->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(T::alignment(type)));
+ descr->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(AssertedCast<int32_t>(T::size(type))));
+ descr->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(T::Opaque));
+ descr->initReservedSlot(JS_DESCR_SLOT_TYPE, Int32Value(type));
+
+ if (!CreateUserSizeAndAlignmentProperties(cx, descr))
+ return false;
+
+ if (!JS_DefineFunctions(cx, descr, T::typeObjectMethods))
+ return false;
+
+ // Create the typed prototype for the scalar type. This winds up
+ // not being user accessible, but we still create one for consistency.
+ Rooted<TypedProto*> proto(cx);
+ proto = NewObjectWithGivenProto<TypedProto>(cx, objProto, TenuredObject);
+ if (!proto)
+ return false;
+ descr->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*proto));
+
+ RootedValue descrValue(cx, ObjectValue(*descr));
+ if (!DefineProperty(cx, module, className, descrValue, nullptr, nullptr, 0))
+ return false;
+
+ if (!CreateTraceList(cx, descr))
+ return false;
+
+ if (!cx->zone()->typeDescrObjects.put(descr)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+template<typename T>
+static JSObject*
+DefineMetaTypeDescr(JSContext* cx,
+ const char* name,
+ Handle<GlobalObject*> global,
+ Handle<TypedObjectModuleObject*> module,
+ TypedObjectModuleObject::Slot protoSlot)
+{
+ RootedAtom className(cx, Atomize(cx, name, strlen(name)));
+ if (!className)
+ return nullptr;
+
+ RootedObject funcProto(cx, global->getOrCreateFunctionPrototype(cx));
+ if (!funcProto)
+ return nullptr;
+
+ // Create ctor.prototype, which inherits from Function.__proto__
+
+ RootedObject proto(cx, NewObjectWithGivenProto<PlainObject>(cx, funcProto, SingletonObject));
+ if (!proto)
+ return nullptr;
+
+ // Create ctor.prototype.prototype, which inherits from Object.__proto__
+
+ RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
+ if (!objProto)
+ return nullptr;
+ RootedObject protoProto(cx);
+ protoProto = NewObjectWithGivenProto<PlainObject>(cx, objProto, SingletonObject);
+ if (!protoProto)
+ return nullptr;
+
+ RootedValue protoProtoValue(cx, ObjectValue(*protoProto));
+ if (!DefineProperty(cx, proto, cx->names().prototype, protoProtoValue,
+ nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+ {
+ return nullptr;
+ }
+
+ // Create ctor itself
+
+ const int constructorLength = 2;
+ RootedFunction ctor(cx);
+ ctor = global->createConstructor(cx, T::construct, className, constructorLength);
+ if (!ctor ||
+ !LinkConstructorAndPrototype(cx, ctor, proto) ||
+ !DefinePropertiesAndFunctions(cx, proto,
+ T::typeObjectProperties,
+ T::typeObjectMethods) ||
+ !DefinePropertiesAndFunctions(cx, protoProto,
+ T::typedObjectProperties,
+ T::typedObjectMethods))
+ {
+ return nullptr;
+ }
+
+ module->initReservedSlot(protoSlot, ObjectValue(*proto));
+
+ return ctor;
+}
+
+/* The initialization strategy for TypedObjects is mildly unusual
+ * compared to other classes. Because all of the types are members
+ * of a single global, `TypedObject`, we basically make the
+ * initializer for the `TypedObject` class populate the
+ * `TypedObject` global (which is referred to as "module" herein).
+ */
+bool
+GlobalObject::initTypedObjectModule(JSContext* cx, Handle<GlobalObject*> global)
+{
+ RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
+ if (!objProto)
+ return false;
+
+ Rooted<TypedObjectModuleObject*> module(cx);
+ module = NewObjectWithGivenProto<TypedObjectModuleObject>(cx, objProto);
+ if (!module)
+ return false;
+
+ if (!JS_DefineFunctions(cx, module, TypedObjectMethods))
+ return false;
+
+ // uint8, uint16, any, etc
+
+#define BINARYDATA_SCALAR_DEFINE(constant_, type_, name_) \
+ if (!DefineSimpleTypeDescr<ScalarTypeDescr>(cx, global, module, constant_, \
+ cx->names().name_)) \
+ return false;
+ JS_FOR_EACH_SCALAR_TYPE_REPR(BINARYDATA_SCALAR_DEFINE)
+#undef BINARYDATA_SCALAR_DEFINE
+
+#define BINARYDATA_REFERENCE_DEFINE(constant_, type_, name_) \
+ if (!DefineSimpleTypeDescr<ReferenceTypeDescr>(cx, global, module, constant_, \
+ cx->names().name_)) \
+ return false;
+ JS_FOR_EACH_REFERENCE_TYPE_REPR(BINARYDATA_REFERENCE_DEFINE)
+#undef BINARYDATA_REFERENCE_DEFINE
+
+ // ArrayType.
+
+ RootedObject arrayType(cx);
+ arrayType = DefineMetaTypeDescr<ArrayMetaTypeDescr>(
+ cx, "ArrayType", global, module, TypedObjectModuleObject::ArrayTypePrototype);
+ if (!arrayType)
+ return false;
+
+ RootedValue arrayTypeValue(cx, ObjectValue(*arrayType));
+ if (!DefineProperty(cx, module, cx->names().ArrayType, arrayTypeValue,
+ nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+ {
+ return false;
+ }
+
+ // StructType.
+
+ RootedObject structType(cx);
+ structType = DefineMetaTypeDescr<StructMetaTypeDescr>(
+ cx, "StructType", global, module, TypedObjectModuleObject::StructTypePrototype);
+ if (!structType)
+ return false;
+
+ RootedValue structTypeValue(cx, ObjectValue(*structType));
+ if (!DefineProperty(cx, module, cx->names().StructType, structTypeValue,
+ nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT))
+ {
+ return false;
+ }
+
+ // Everything is setup, install module on the global object:
+ RootedValue moduleValue(cx, ObjectValue(*module));
+ if (!DefineProperty(cx, global, cx->names().TypedObject, moduleValue, nullptr, nullptr,
+ JSPROP_RESOLVING))
+ {
+ return false;
+ }
+
+ global->setConstructor(JSProto_TypedObject, moduleValue);
+
+ return module;
+}
+
+JSObject*
+js::InitTypedObjectModuleObject(JSContext* cx, HandleObject obj)
+{
+ MOZ_ASSERT(obj->is<GlobalObject>());
+ Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+ return global->getOrCreateTypedObjectModule(cx);
+}
+
+/******************************************************************************
+ * Typed objects
+ */
+
+uint32_t
+TypedObject::offset() const
+{
+ if (is<InlineTypedObject>())
+ return 0;
+ return PointerRangeSize(typedMemBase(), typedMem());
+}
+
+uint32_t
+TypedObject::length() const
+{
+ return typeDescr().as<ArrayTypeDescr>().length();
+}
+
+uint8_t*
+TypedObject::typedMem() const
+{
+ MOZ_ASSERT(isAttached());
+
+ if (is<InlineTypedObject>())
+ return as<InlineTypedObject>().inlineTypedMem();
+ return as<OutlineTypedObject>().outOfLineTypedMem();
+}
+
+uint8_t*
+TypedObject::typedMemBase() const
+{
+ MOZ_ASSERT(isAttached());
+ MOZ_ASSERT(is<OutlineTypedObject>());
+
+ JSObject& owner = as<OutlineTypedObject>().owner();
+ if (owner.is<ArrayBufferObject>())
+ return owner.as<ArrayBufferObject>().dataPointer();
+ return owner.as<InlineTypedObject>().inlineTypedMem();
+}
+
+bool
+TypedObject::isAttached() const
+{
+ if (is<InlineTransparentTypedObject>()) {
+ ObjectWeakMap* table = compartment()->lazyArrayBuffers;
+ if (table) {
+ JSObject* buffer = table->lookup(this);
+ if (buffer)
+ return !buffer->as<ArrayBufferObject>().isDetached();
+ }
+ return true;
+ }
+ if (is<InlineOpaqueTypedObject>())
+ return true;
+ if (!as<OutlineTypedObject>().outOfLineTypedMem())
+ return false;
+ JSObject& owner = as<OutlineTypedObject>().owner();
+ if (owner.is<ArrayBufferObject>() && owner.as<ArrayBufferObject>().isDetached())
+ return false;
+ return true;
+}
+
+/* static */ bool
+TypedObject::GetBuffer(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ JSObject& obj = args[0].toObject();
+ ArrayBufferObject* buffer;
+ if (obj.is<OutlineTransparentTypedObject>())
+ buffer = obj.as<OutlineTransparentTypedObject>().getOrCreateBuffer(cx);
+ else
+ buffer = obj.as<InlineTransparentTypedObject>().getOrCreateBuffer(cx);
+ if (!buffer)
+ return false;
+ args.rval().setObject(*buffer);
+ return true;
+}
+
+/* static */ bool
+TypedObject::GetByteOffset(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setInt32(AssertedCast<int32_t>(args[0].toObject().as<TypedObject>().offset()));
+ return true;
+}
+
+/******************************************************************************
+ * Outline typed objects
+ */
+
+/*static*/ OutlineTypedObject*
+OutlineTypedObject::createUnattached(JSContext* cx,
+ HandleTypeDescr descr,
+ int32_t length,
+ gc::InitialHeap heap)
+{
+ if (descr->opaque())
+ return createUnattachedWithClass(cx, &OutlineOpaqueTypedObject::class_, descr, length, heap);
+ else
+ return createUnattachedWithClass(cx, &OutlineTransparentTypedObject::class_, descr, length, heap);
+}
+
+void
+OutlineTypedObject::setOwnerAndData(JSObject* owner, uint8_t* data)
+{
+ // Make sure we don't associate with array buffers whose data is from an
+ // inline typed object, see obj_trace.
+ MOZ_ASSERT_IF(owner && owner->is<ArrayBufferObject>(),
+ !owner->as<ArrayBufferObject>().forInlineTypedObject());
+
+ // Typed objects cannot move from one owner to another, so don't worry
+ // about pre barriers during this initialization.
+ owner_ = owner;
+ data_ = data;
+
+ // Trigger a post barrier when attaching an object outside the nursery to
+ // one that is inside it.
+ if (owner && !IsInsideNursery(this) && IsInsideNursery(owner))
+ runtimeFromMainThread()->gc.storeBuffer.putWholeCell(this);
+}
+
+/*static*/ OutlineTypedObject*
+OutlineTypedObject::createUnattachedWithClass(JSContext* cx,
+ const Class* clasp,
+ HandleTypeDescr descr,
+ int32_t length,
+ gc::InitialHeap heap)
+{
+ MOZ_ASSERT(clasp == &OutlineTransparentTypedObject::class_ ||
+ clasp == &OutlineOpaqueTypedObject::class_);
+
+ RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, clasp,
+ TaggedProto(&descr->typedProto()),
+ descr));
+ if (!group)
+ return nullptr;
+
+ NewObjectKind newKind = (heap == gc::TenuredHeap) ? TenuredObject : GenericObject;
+ OutlineTypedObject* obj = NewObjectWithGroup<OutlineTypedObject>(cx, group,
+ gc::AllocKind::OBJECT0,
+ newKind);
+ if (!obj)
+ return nullptr;
+
+ obj->setOwnerAndData(nullptr, nullptr);
+ return obj;
+}
+
+void
+OutlineTypedObject::attach(JSContext* cx, ArrayBufferObject& buffer, uint32_t offset)
+{
+ MOZ_ASSERT(!isAttached());
+ MOZ_ASSERT(offset <= buffer.byteLength());
+ MOZ_ASSERT(size() <= buffer.byteLength() - offset);
+
+ // If the owner's data is from an inline typed object, associate this with
+ // the inline typed object instead, to simplify tracing.
+ if (buffer.forInlineTypedObject()) {
+ InlineTypedObject& realOwner = buffer.firstView()->as<InlineTypedObject>();
+ attach(cx, realOwner, offset);
+ return;
+ }
+
+ buffer.setHasTypedObjectViews();
+
+ {
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (!buffer.addView(cx, this))
+ oomUnsafe.crash("TypedObject::attach");
+ }
+
+ setOwnerAndData(&buffer, buffer.dataPointer() + offset);
+}
+
+void
+OutlineTypedObject::attach(JSContext* cx, TypedObject& typedObj, uint32_t offset)
+{
+ MOZ_ASSERT(!isAttached());
+ MOZ_ASSERT(typedObj.isAttached());
+
+ JSObject* owner = &typedObj;
+ if (typedObj.is<OutlineTypedObject>()) {
+ owner = &typedObj.as<OutlineTypedObject>().owner();
+ MOZ_ASSERT(typedObj.offset() <= UINT32_MAX - offset);
+ offset += typedObj.offset();
+ }
+
+ if (owner->is<ArrayBufferObject>()) {
+ attach(cx, owner->as<ArrayBufferObject>(), offset);
+ } else {
+ MOZ_ASSERT(owner->is<InlineTypedObject>());
+ JS::AutoCheckCannotGC nogc(cx);
+ setOwnerAndData(owner, owner->as<InlineTypedObject>().inlineTypedMem(nogc) + offset);
+ }
+}
+
+// Returns a suitable JS_TYPEDOBJ_SLOT_LENGTH value for an instance of
+// the type `type`.
+static uint32_t
+TypedObjLengthFromType(TypeDescr& descr)
+{
+ switch (descr.kind()) {
+ case type::Scalar:
+ case type::Reference:
+ case type::Struct:
+ case type::Simd:
+ return 0;
+
+ case type::Array:
+ return descr.as<ArrayTypeDescr>().length();
+ }
+ MOZ_CRASH("Invalid kind");
+}
+
+/*static*/ OutlineTypedObject*
+OutlineTypedObject::createDerived(JSContext* cx, HandleTypeDescr type,
+ HandleTypedObject typedObj, uint32_t offset)
+{
+ MOZ_ASSERT(offset <= typedObj->size());
+ MOZ_ASSERT(offset + type->size() <= typedObj->size());
+
+ int32_t length = TypedObjLengthFromType(*type);
+
+ const js::Class* clasp = typedObj->opaque()
+ ? &OutlineOpaqueTypedObject::class_
+ : &OutlineTransparentTypedObject::class_;
+ Rooted<OutlineTypedObject*> obj(cx);
+ obj = createUnattachedWithClass(cx, clasp, type, length);
+ if (!obj)
+ return nullptr;
+
+ obj->attach(cx, *typedObj, offset);
+ return obj;
+}
+
+/*static*/ TypedObject*
+TypedObject::createZeroed(JSContext* cx, HandleTypeDescr descr, int32_t length, gc::InitialHeap heap)
+{
+ // If possible, create an object with inline data.
+ if (descr->size() <= InlineTypedObject::MaximumSize) {
+ AutoSetNewObjectMetadata metadata(cx);
+
+ InlineTypedObject* obj = InlineTypedObject::create(cx, descr, heap);
+ if (!obj)
+ return nullptr;
+ JS::AutoCheckCannotGC nogc(cx);
+ descr->initInstances(cx->runtime(), obj->inlineTypedMem(nogc), 1);
+ return obj;
+ }
+
+ // Create unattached wrapper object.
+ Rooted<OutlineTypedObject*> obj(cx, OutlineTypedObject::createUnattached(cx, descr, length, heap));
+ if (!obj)
+ return nullptr;
+
+ // Allocate and initialize the memory for this instance.
+ size_t totalSize = descr->size();
+ Rooted<ArrayBufferObject*> buffer(cx);
+ buffer = ArrayBufferObject::create(cx, totalSize);
+ if (!buffer)
+ return nullptr;
+ descr->initInstances(cx->runtime(), buffer->dataPointer(), 1);
+ obj->attach(cx, *buffer, 0);
+ return obj;
+}
+
+static bool
+ReportTypedObjTypeError(JSContext* cx,
+ const unsigned errorNumber,
+ HandleTypedObject obj)
+{
+ // Serialize type string of obj
+ RootedAtom typeReprAtom(cx, &obj->typeDescr().stringRepr());
+ UniqueChars typeReprStr(JS_EncodeStringToUTF8(cx, typeReprAtom));
+ if (!typeReprStr)
+ return false;
+
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber, typeReprStr.get());
+ return false;
+}
+
+/* static */ void
+OutlineTypedObject::obj_trace(JSTracer* trc, JSObject* object)
+{
+ OutlineTypedObject& typedObj = object->as<OutlineTypedObject>();
+
+ TraceEdge(trc, &typedObj.shape_, "OutlineTypedObject_shape");
+
+ if (!typedObj.owner_)
+ return;
+
+ TypeDescr& descr = typedObj.typeDescr();
+
+ // Mark the owner, watching in case it is moved by the tracer.
+ JSObject* oldOwner = typedObj.owner_;
+ TraceManuallyBarrieredEdge(trc, &typedObj.owner_, "typed object owner");
+ JSObject* owner = typedObj.owner_;
+
+ uint8_t* oldData = typedObj.outOfLineTypedMem();
+ uint8_t* newData = oldData;
+
+ // Update the data pointer if the owner moved and the owner's data is
+ // inline with it. Note that an array buffer pointing to data in an inline
+ // typed object will never be used as an owner for another outline typed
+ // object. In such cases, the owner will be the inline typed object itself.
+ MakeAccessibleAfterMovingGC(owner);
+ MOZ_ASSERT_IF(owner->is<ArrayBufferObject>(),
+ !owner->as<ArrayBufferObject>().forInlineTypedObject());
+ if (owner != oldOwner &&
+ (owner->is<InlineTypedObject>() ||
+ owner->as<ArrayBufferObject>().hasInlineData()))
+ {
+ newData += reinterpret_cast<uint8_t*>(owner) - reinterpret_cast<uint8_t*>(oldOwner);
+ typedObj.setData(newData);
+
+ trc->runtime()->gc.nursery.maybeSetForwardingPointer(trc, oldData, newData, /* direct = */ false);
+ }
+
+ if (!descr.opaque() || !typedObj.isAttached())
+ return;
+
+ descr.traceInstances(trc, newData, 1);
+}
+
+bool
+TypeDescr::hasProperty(const JSAtomState& names, jsid id)
+{
+ switch (kind()) {
+ case type::Scalar:
+ case type::Reference:
+ case type::Simd:
+ return false;
+
+ case type::Array:
+ {
+ uint32_t index;
+ return IdIsIndex(id, &index) || JSID_IS_ATOM(id, names.length);
+ }
+
+ case type::Struct:
+ {
+ size_t index;
+ return as<StructTypeDescr>().fieldIndex(id, &index);
+ }
+ }
+
+ MOZ_CRASH("Unexpected kind");
+}
+
+/* static */ bool
+TypedObject::obj_lookupProperty(JSContext* cx, HandleObject obj, HandleId id,
+ MutableHandleObject objp, MutableHandleShape propp)
+{
+ if (obj->as<TypedObject>().typeDescr().hasProperty(cx->names(), id)) {
+ MarkNonNativePropertyFound<CanGC>(propp);
+ objp.set(obj);
+ return true;
+ }
+
+ RootedObject proto(cx, obj->staticPrototype());
+ if (!proto) {
+ objp.set(nullptr);
+ propp.set(nullptr);
+ return true;
+ }
+
+ return LookupProperty(cx, proto, id, objp, propp);
+}
+
+static bool
+ReportPropertyError(JSContext* cx,
+ const unsigned errorNumber,
+ HandleId id)
+{
+ RootedValue idVal(cx, IdToValue(id));
+ RootedString str(cx, ValueToSource(cx, idVal));
+ if (!str)
+ return false;
+
+ UniqueChars propName(JS_EncodeStringToUTF8(cx, str));
+ if (!propName)
+ return false;
+
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber, propName.get());
+ return false;
+}
+
+bool
+TypedObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result)
+{
+ Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());
+ return ReportTypedObjTypeError(cx, JSMSG_OBJECT_NOT_EXTENSIBLE, typedObj);
+}
+
+bool
+TypedObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
+{
+ Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());
+ switch (typedObj->typeDescr().kind()) {
+ case type::Scalar:
+ case type::Reference:
+ case type::Simd:
+ break;
+
+ case type::Array: {
+ if (JSID_IS_ATOM(id, cx->names().length)) {
+ *foundp = true;
+ return true;
+ }
+ uint32_t index;
+ // Elements are not inherited from the prototype.
+ if (IdIsIndex(id, &index)) {
+ *foundp = (index < uint32_t(typedObj->length()));
+ return true;
+ }
+ break;
+ }
+
+ case type::Struct:
+ size_t index;
+ if (typedObj->typeDescr().as<StructTypeDescr>().fieldIndex(id, &index)) {
+ *foundp = true;
+ return true;
+ }
+ }
+
+ RootedObject proto(cx, obj->staticPrototype());
+ if (!proto) {
+ *foundp = false;
+ return true;
+ }
+
+ return HasProperty(cx, proto, id, foundp);
+}
+
+bool
+TypedObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
+ HandleId id, MutableHandleValue vp)
+{
+ Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());
+
+ // Dispatch elements to obj_getElement:
+ uint32_t index;
+ if (IdIsIndex(id, &index))
+ return obj_getElement(cx, obj, receiver, index, vp);
+
+ // Handle everything else here:
+
+ switch (typedObj->typeDescr().kind()) {
+ case type::Scalar:
+ case type::Reference:
+ break;
+
+ case type::Simd:
+ break;
+
+ case type::Array:
+ if (JSID_IS_ATOM(id, cx->names().length)) {
+ if (!typedObj->isAttached()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED);
+ return false;
+ }
+
+ vp.setInt32(typedObj->length());
+ return true;
+ }
+ break;
+
+ case type::Struct: {
+ Rooted<StructTypeDescr*> descr(cx, &typedObj->typeDescr().as<StructTypeDescr>());
+
+ size_t fieldIndex;
+ if (!descr->fieldIndex(id, &fieldIndex))
+ break;
+
+ size_t offset = descr->fieldOffset(fieldIndex);
+ Rooted<TypeDescr*> fieldType(cx, &descr->fieldDescr(fieldIndex));
+ return Reify(cx, fieldType, typedObj, offset, vp);
+ }
+ }
+
+ RootedObject proto(cx, obj->staticPrototype());
+ if (!proto) {
+ vp.setUndefined();
+ return true;
+ }
+
+ return GetProperty(cx, proto, receiver, id, vp);
+}
+
+bool
+TypedObject::obj_getElement(JSContext* cx, HandleObject obj, HandleValue receiver,
+ uint32_t index, MutableHandleValue vp)
+{
+ MOZ_ASSERT(obj->is<TypedObject>());
+ Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());
+ Rooted<TypeDescr*> descr(cx, &typedObj->typeDescr());
+
+ switch (descr->kind()) {
+ case type::Scalar:
+ case type::Reference:
+ case type::Simd:
+ case type::Struct:
+ break;
+
+ case type::Array:
+ return obj_getArrayElement(cx, typedObj, descr, index, vp);
+ }
+
+ RootedObject proto(cx, obj->staticPrototype());
+ if (!proto) {
+ vp.setUndefined();
+ return true;
+ }
+
+ return GetElement(cx, proto, receiver, index, vp);
+}
+
+/*static*/ bool
+TypedObject::obj_getArrayElement(JSContext* cx,
+ Handle<TypedObject*> typedObj,
+ Handle<TypeDescr*> typeDescr,
+ uint32_t index,
+ MutableHandleValue vp)
+{
+ // Elements are not inherited from the prototype.
+ if (index >= (size_t) typedObj->length()) {
+ vp.setUndefined();
+ return true;
+ }
+
+ Rooted<TypeDescr*> elementType(cx, &typeDescr->as<ArrayTypeDescr>().elementType());
+ size_t offset = elementType->size() * index;
+ return Reify(cx, elementType, typedObj, offset, vp);
+}
+
+bool
+TypedObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result)
+{
+ Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());
+
+ switch (typedObj->typeDescr().kind()) {
+ case type::Scalar:
+ case type::Reference:
+ break;
+
+ case type::Simd:
+ break;
+
+ case type::Array: {
+ if (JSID_IS_ATOM(id, cx->names().length)) {
+ if (receiver.isObject() && obj == &receiver.toObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_CANT_REDEFINE_ARRAY_LENGTH);
+ return false;
+ }
+ return result.failReadOnly();
+ }
+
+ uint32_t index;
+ if (IdIsIndex(id, &index)) {
+ if (!receiver.isObject() || obj != &receiver.toObject())
+ return SetPropertyByDefining(cx, id, v, receiver, result);
+
+ if (index >= uint32_t(typedObj->length())) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TYPEDOBJECT_BINARYARRAY_BAD_INDEX);
+ return false;
+ }
+
+ Rooted<TypeDescr*> elementType(cx);
+ elementType = &typedObj->typeDescr().as<ArrayTypeDescr>().elementType();
+ size_t offset = elementType->size() * index;
+ if (!ConvertAndCopyTo(cx, elementType, typedObj, offset, nullptr, v))
+ return false;
+ return result.succeed();
+ }
+ break;
+ }
+
+ case type::Struct: {
+ Rooted<StructTypeDescr*> descr(cx, &typedObj->typeDescr().as<StructTypeDescr>());
+
+ size_t fieldIndex;
+ if (!descr->fieldIndex(id, &fieldIndex))
+ break;
+
+ if (!receiver.isObject() || obj != &receiver.toObject())
+ return SetPropertyByDefining(cx, id, v, receiver, result);
+
+ size_t offset = descr->fieldOffset(fieldIndex);
+ Rooted<TypeDescr*> fieldType(cx, &descr->fieldDescr(fieldIndex));
+ RootedAtom fieldName(cx, &descr->fieldName(fieldIndex));
+ if (!ConvertAndCopyTo(cx, fieldType, typedObj, offset, fieldName, v))
+ return false;
+ return result.succeed();
+ }
+ }
+
+ return SetPropertyOnProto(cx, obj, id, v, receiver, result);
+}
+
+bool
+TypedObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
+ MutableHandle<PropertyDescriptor> desc)
+{
+ Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());
+ if (!typedObj->isAttached()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED);
+ return false;
+ }
+
+ Rooted<TypeDescr*> descr(cx, &typedObj->typeDescr());
+ switch (descr->kind()) {
+ case type::Scalar:
+ case type::Reference:
+ case type::Simd:
+ break;
+
+ case type::Array:
+ {
+ uint32_t index;
+ if (IdIsIndex(id, &index)) {
+ if (!obj_getArrayElement(cx, typedObj, descr, index, desc.value()))
+ return false;
+ desc.setAttributes(JSPROP_ENUMERATE | JSPROP_PERMANENT);
+ desc.object().set(obj);
+ return true;
+ }
+
+ if (JSID_IS_ATOM(id, cx->names().length)) {
+ desc.value().setInt32(typedObj->length());
+ desc.setAttributes(JSPROP_READONLY | JSPROP_PERMANENT);
+ desc.object().set(obj);
+ return true;
+ }
+ break;
+ }
+
+ case type::Struct:
+ {
+ Rooted<StructTypeDescr*> descr(cx, &typedObj->typeDescr().as<StructTypeDescr>());
+
+ size_t fieldIndex;
+ if (!descr->fieldIndex(id, &fieldIndex))
+ break;
+
+ size_t offset = descr->fieldOffset(fieldIndex);
+ Rooted<TypeDescr*> fieldType(cx, &descr->fieldDescr(fieldIndex));
+ if (!Reify(cx, fieldType, typedObj, offset, desc.value()))
+ return false;
+
+ desc.setAttributes(JSPROP_ENUMERATE | JSPROP_PERMANENT);
+ desc.object().set(obj);
+ return true;
+ }
+ }
+
+ desc.object().set(nullptr);
+ return true;
+}
+
+static bool
+IsOwnId(JSContext* cx, HandleObject obj, HandleId id)
+{
+ uint32_t index;
+ Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());
+ switch (typedObj->typeDescr().kind()) {
+ case type::Scalar:
+ case type::Reference:
+ case type::Simd:
+ return false;
+
+ case type::Array:
+ return IdIsIndex(id, &index) || JSID_IS_ATOM(id, cx->names().length);
+
+ case type::Struct:
+ size_t index;
+ if (typedObj->typeDescr().as<StructTypeDescr>().fieldIndex(id, &index))
+ return true;
+ }
+
+ return false;
+}
+
+bool
+TypedObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result)
+{
+ if (IsOwnId(cx, obj, id))
+ return ReportPropertyError(cx, JSMSG_CANT_DELETE, id);
+
+ RootedObject proto(cx, obj->staticPrototype());
+ if (!proto)
+ return result.succeed();
+
+ return DeleteProperty(cx, proto, id, result);
+}
+
+bool
+TypedObject::obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
+ bool enumerableOnly)
+{
+ MOZ_ASSERT(obj->is<TypedObject>());
+ Rooted<TypedObject*> typedObj(cx, &obj->as<TypedObject>());
+ Rooted<TypeDescr*> descr(cx, &typedObj->typeDescr());
+
+ RootedId id(cx);
+ switch (descr->kind()) {
+ case type::Scalar:
+ case type::Reference:
+ case type::Simd: {
+ // Nothing to enumerate.
+ break;
+ }
+
+ case type::Array: {
+ if (!properties.reserve(typedObj->length()))
+ return false;
+
+ for (uint32_t index = 0; index < typedObj->length(); index++) {
+ id = INT_TO_JSID(index);
+ properties.infallibleAppend(id);
+ }
+ break;
+ }
+
+ case type::Struct: {
+ size_t fieldCount = descr->as<StructTypeDescr>().fieldCount();
+ if (!properties.reserve(fieldCount))
+ return false;
+
+ for (size_t index = 0; index < fieldCount; index++) {
+ id = AtomToId(&descr->as<StructTypeDescr>().fieldName(index));
+ properties.infallibleAppend(id);
+ }
+ break;
+ }
+ }
+
+ return true;
+}
+
+void
+OutlineTypedObject::notifyBufferDetached(void* newData)
+{
+ setData(reinterpret_cast<uint8_t*>(newData));
+}
+
+/******************************************************************************
+ * Inline typed objects
+ */
+
+/* static */ InlineTypedObject*
+InlineTypedObject::create(JSContext* cx, HandleTypeDescr descr, gc::InitialHeap heap)
+{
+ gc::AllocKind allocKind = allocKindForTypeDescriptor(descr);
+
+ const Class* clasp = descr->opaque()
+ ? &InlineOpaqueTypedObject::class_
+ : &InlineTransparentTypedObject::class_;
+
+ RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, clasp,
+ TaggedProto(&descr->typedProto()),
+ descr));
+ if (!group)
+ return nullptr;
+
+ NewObjectKind newKind = (heap == gc::TenuredHeap) ? TenuredObject : GenericObject;
+ return NewObjectWithGroup<InlineTypedObject>(cx, group, allocKind, newKind);
+}
+
+/* static */ InlineTypedObject*
+InlineTypedObject::createCopy(JSContext* cx, Handle<InlineTypedObject*> templateObject,
+ gc::InitialHeap heap)
+{
+ AutoSetNewObjectMetadata metadata(cx);
+
+ Rooted<TypeDescr*> descr(cx, &templateObject->typeDescr());
+ InlineTypedObject* res = create(cx, descr, heap);
+ if (!res)
+ return nullptr;
+
+ memcpy(res->inlineTypedMem(), templateObject->inlineTypedMem(), templateObject->size());
+ return res;
+}
+
+/* static */ void
+InlineTypedObject::obj_trace(JSTracer* trc, JSObject* object)
+{
+ InlineTypedObject& typedObj = object->as<InlineTypedObject>();
+
+ TraceEdge(trc, &typedObj.shape_, "InlineTypedObject_shape");
+
+ // Inline transparent objects do not have references and do not need more
+ // tracing. If there is an entry in the compartment's LazyArrayBufferTable,
+ // tracing that reference will be taken care of by the table itself.
+ if (typedObj.is<InlineTransparentTypedObject>())
+ return;
+
+ typedObj.typeDescr().traceInstances(trc, typedObj.inlineTypedMem(), 1);
+}
+
+/* static */ void
+InlineTypedObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src)
+{
+ // Inline typed object element arrays can be preserved on the stack by Ion
+ // and need forwarding pointers created during a minor GC. We can't do this
+ // in the trace hook because we don't have any stale data to determine
+ // whether this object moved and where it was moved from.
+ TypeDescr& descr = dst->as<InlineTypedObject>().typeDescr();
+ if (descr.kind() == type::Array) {
+ // The forwarding pointer can be direct as long as there is enough
+ // space for it. Other objects might point into the object's buffer,
+ // but they will not set any direct forwarding pointers.
+ uint8_t* oldData = reinterpret_cast<uint8_t*>(src) + offsetOfDataStart();
+ uint8_t* newData = dst->as<InlineTypedObject>().inlineTypedMem();
+ trc->runtime()->gc.nursery.maybeSetForwardingPointer(trc, oldData, newData,
+ descr.size() >= sizeof(uintptr_t));
+ }
+}
+
+ArrayBufferObject*
+InlineTransparentTypedObject::getOrCreateBuffer(JSContext* cx)
+{
+ ObjectWeakMap*& table = cx->compartment()->lazyArrayBuffers;
+ if (!table) {
+ table = cx->new_<ObjectWeakMap>(cx);
+ if (!table || !table->init())
+ return nullptr;
+ }
+
+ JSObject* obj = table->lookup(this);
+ if (obj)
+ return &obj->as<ArrayBufferObject>();
+
+ ArrayBufferObject::BufferContents contents =
+ ArrayBufferObject::BufferContents::createPlain(inlineTypedMem());
+ size_t nbytes = typeDescr().size();
+
+ // Prevent GC under ArrayBufferObject::create, which might move this object
+ // and its contents.
+ gc::AutoSuppressGC suppress(cx);
+
+ ArrayBufferObject* buffer =
+ ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::DoesntOwnData);
+ if (!buffer)
+ return nullptr;
+
+ // The owning object must always be the array buffer's first view. This
+ // both prevents the memory from disappearing out from under the buffer
+ // (the first view is held strongly by the buffer) and is used by the
+ // buffer marking code to detect whether its data pointer needs to be
+ // relocated.
+ JS_ALWAYS_TRUE(buffer->addView(cx, this));
+
+ buffer->setForInlineTypedObject();
+ buffer->setHasTypedObjectViews();
+
+ if (!table->add(cx, this, buffer))
+ return nullptr;
+
+ if (IsInsideNursery(this)) {
+ // Make sure the buffer is traced by the next generational collection,
+ // so that its data pointer is updated after this typed object moves.
+ cx->runtime()->gc.storeBuffer.putWholeCell(buffer);
+ }
+
+ return buffer;
+}
+
+ArrayBufferObject*
+OutlineTransparentTypedObject::getOrCreateBuffer(JSContext* cx)
+{
+ if (owner().is<ArrayBufferObject>())
+ return &owner().as<ArrayBufferObject>();
+ return owner().as<InlineTransparentTypedObject>().getOrCreateBuffer(cx);
+}
+
+/******************************************************************************
+ * Typed object classes
+ */
+
+const ObjectOps TypedObject::objectOps_ = {
+ TypedObject::obj_lookupProperty,
+ TypedObject::obj_defineProperty,
+ TypedObject::obj_hasProperty,
+ TypedObject::obj_getProperty,
+ TypedObject::obj_setProperty,
+ TypedObject::obj_getOwnPropertyDescriptor,
+ TypedObject::obj_deleteProperty,
+ nullptr, nullptr, /* watch/unwatch */
+ nullptr, /* getElements */
+ TypedObject::obj_enumerate,
+ nullptr, /* thisValue */
+};
+
+#define DEFINE_TYPEDOBJ_CLASS(Name, Trace, flag) \
+ static const ClassOps Name##ClassOps = { \
+ nullptr, /* addProperty */ \
+ nullptr, /* delProperty */ \
+ nullptr, /* getProperty */ \
+ nullptr, /* setProperty */ \
+ nullptr, /* enumerate */ \
+ nullptr, /* resolve */ \
+ nullptr, /* mayResolve */ \
+ nullptr, /* finalize */ \
+ nullptr, /* call */ \
+ nullptr, /* hasInstance */ \
+ nullptr, /* construct */ \
+ Trace, \
+ }; \
+ const Class Name::class_ = { \
+ # Name, \
+ Class::NON_NATIVE | flag, \
+ &Name##ClassOps, \
+ JS_NULL_CLASS_SPEC, \
+ JS_NULL_CLASS_EXT, \
+ &TypedObject::objectOps_ \
+ }
+
+DEFINE_TYPEDOBJ_CLASS(OutlineTransparentTypedObject, OutlineTypedObject::obj_trace, 0);
+DEFINE_TYPEDOBJ_CLASS(OutlineOpaqueTypedObject, OutlineTypedObject::obj_trace, 0);
+DEFINE_TYPEDOBJ_CLASS(InlineTransparentTypedObject, InlineTypedObject::obj_trace,
+ JSCLASS_DELAY_METADATA_BUILDER);
+DEFINE_TYPEDOBJ_CLASS(InlineOpaqueTypedObject, InlineTypedObject::obj_trace,
+ JSCLASS_DELAY_METADATA_BUILDER);
+
+static int32_t
+LengthForType(TypeDescr& descr)
+{
+ switch (descr.kind()) {
+ case type::Scalar:
+ case type::Reference:
+ case type::Struct:
+ case type::Simd:
+ return 0;
+
+ case type::Array:
+ return descr.as<ArrayTypeDescr>().length();
+ }
+
+ MOZ_CRASH("Invalid kind");
+}
+
+static bool
+CheckOffset(uint32_t offset, uint32_t size, uint32_t alignment, uint32_t bufferLength)
+{
+ // Offset (plus size) must be fully contained within the buffer.
+ if (offset > bufferLength)
+ return false;
+ if (offset + size < offset)
+ return false;
+ if (offset + size > bufferLength)
+ return false;
+
+ // Offset must be aligned.
+ if ((offset % alignment) != 0)
+ return false;
+
+ return true;
+}
+
+template<typename T, typename U, typename V, typename W>
+inline bool CheckOffset(T, U, V, W) = delete;
+
+/*static*/ bool
+TypedObject::construct(JSContext* cx, unsigned int argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ MOZ_ASSERT(args.callee().is<TypeDescr>());
+ Rooted<TypeDescr*> callee(cx, &args.callee().as<TypeDescr>());
+
+ // Typed object constructors are overloaded in three ways, in order of
+ // precedence:
+ //
+ // new TypeObj()
+ // new TypeObj(buffer, [offset])
+ // new TypeObj(data)
+
+ // Zero argument constructor:
+ if (args.length() == 0) {
+ int32_t length = LengthForType(*callee);
+ Rooted<TypedObject*> obj(cx, createZeroed(cx, callee, length));
+ if (!obj)
+ return false;
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ // Buffer constructor.
+ if (args[0].isObject() && args[0].toObject().is<ArrayBufferObject>()) {
+ Rooted<ArrayBufferObject*> buffer(cx);
+ buffer = &args[0].toObject().as<ArrayBufferObject>();
+
+ if (callee->opaque() || buffer->isDetached()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS);
+ return false;
+ }
+
+ uint32_t offset;
+ if (args.length() >= 2 && !args[1].isUndefined()) {
+ if (!args[1].isInt32() || args[1].toInt32() < 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS);
+ return false;
+ }
+
+ offset = args[1].toInt32();
+ } else {
+ offset = 0;
+ }
+
+ if (args.length() >= 3 && !args[2].isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS);
+ return false;
+ }
+
+ if (!CheckOffset(offset, callee->size(), callee->alignment(),
+ buffer->byteLength()))
+ {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS);
+ return false;
+ }
+
+ Rooted<OutlineTypedObject*> obj(cx);
+ obj = OutlineTypedObject::createUnattached(cx, callee, LengthForType(*callee));
+ if (!obj)
+ return false;
+
+ obj->attach(cx, *buffer, offset);
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ // Data constructor.
+ if (args[0].isObject()) {
+ // Create the typed object.
+ int32_t length = LengthForType(*callee);
+ Rooted<TypedObject*> obj(cx, createZeroed(cx, callee, length));
+ if (!obj)
+ return false;
+
+ // Initialize from `arg`.
+ if (!ConvertAndCopyTo(cx, obj, args[0]))
+ return false;
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ // Something bogus.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS);
+ return false;
+}
+
+/******************************************************************************
+ * Intrinsics
+ */
+
+bool
+js::NewOpaqueTypedObject(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(args[0].isObject() && args[0].toObject().is<TypeDescr>());
+
+ Rooted<TypeDescr*> descr(cx, &args[0].toObject().as<TypeDescr>());
+ int32_t length = TypedObjLengthFromType(*descr);
+ Rooted<OutlineTypedObject*> obj(cx);
+ obj = OutlineTypedObject::createUnattachedWithClass(cx, &OutlineOpaqueTypedObject::class_, descr, length);
+ if (!obj)
+ return false;
+ args.rval().setObject(*obj);
+ return true;
+}
+
+bool
+js::NewDerivedTypedObject(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 3);
+ MOZ_ASSERT(args[0].isObject() && args[0].toObject().is<TypeDescr>());
+ MOZ_ASSERT(args[1].isObject() && args[1].toObject().is<TypedObject>());
+ MOZ_ASSERT(args[2].isInt32());
+
+ Rooted<TypeDescr*> descr(cx, &args[0].toObject().as<TypeDescr>());
+ Rooted<TypedObject*> typedObj(cx, &args[1].toObject().as<TypedObject>());
+ uint32_t offset = AssertedCast<uint32_t>(args[2].toInt32());
+
+ Rooted<TypedObject*> obj(cx);
+ obj = OutlineTypedObject::createDerived(cx, descr, typedObj, offset);
+ if (!obj)
+ return false;
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+bool
+js::AttachTypedObject(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 3);
+ MOZ_ASSERT(args[2].isInt32());
+
+ OutlineTypedObject& handle = args[0].toObject().as<OutlineTypedObject>();
+ TypedObject& target = args[1].toObject().as<TypedObject>();
+ MOZ_ASSERT(!handle.isAttached());
+ uint32_t offset = AssertedCast<uint32_t>(args[2].toInt32());
+
+ handle.attach(cx, target, offset);
+
+ return true;
+}
+
+bool
+js::SetTypedObjectOffset(JSContext*, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 2);
+ MOZ_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>());
+ MOZ_ASSERT(args[1].isInt32());
+
+ OutlineTypedObject& typedObj = args[0].toObject().as<OutlineTypedObject>();
+ int32_t offset = args[1].toInt32();
+
+ MOZ_ASSERT(typedObj.isAttached());
+ typedObj.resetOffset(offset);
+ args.rval().setUndefined();
+ return true;
+}
+
+bool
+js::ObjectIsTypeDescr(JSContext*, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(args[0].isObject());
+ args.rval().setBoolean(args[0].toObject().is<TypeDescr>());
+ return true;
+}
+
+bool
+js::ObjectIsTypedObject(JSContext*, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(args[0].isObject());
+ args.rval().setBoolean(args[0].toObject().is<TypedObject>());
+ return true;
+}
+
+bool
+js::ObjectIsOpaqueTypedObject(JSContext*, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ JSObject& obj = args[0].toObject();
+ args.rval().setBoolean(obj.is<TypedObject>() && obj.as<TypedObject>().opaque());
+ return true;
+}
+
+bool
+js::ObjectIsTransparentTypedObject(JSContext*, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ JSObject& obj = args[0].toObject();
+ args.rval().setBoolean(obj.is<TypedObject>() && !obj.as<TypedObject>().opaque());
+ return true;
+}
+
+bool
+js::TypeDescrIsSimpleType(JSContext*, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(args[0].isObject());
+ MOZ_ASSERT(args[0].toObject().is<js::TypeDescr>());
+ args.rval().setBoolean(args[0].toObject().is<js::SimpleTypeDescr>());
+ return true;
+}
+
+bool
+js::TypeDescrIsArrayType(JSContext*, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(args[0].isObject());
+ MOZ_ASSERT(args[0].toObject().is<js::TypeDescr>());
+ JSObject& obj = args[0].toObject();
+ args.rval().setBoolean(obj.is<js::ArrayTypeDescr>());
+ return true;
+}
+
+bool
+js::TypedObjectIsAttached(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ TypedObject& typedObj = args[0].toObject().as<TypedObject>();
+ args.rval().setBoolean(typedObj.isAttached());
+ return true;
+}
+
+bool
+js::TypedObjectTypeDescr(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ TypedObject& typedObj = args[0].toObject().as<TypedObject>();
+ args.rval().setObject(typedObj.typeDescr());
+ return true;
+}
+
+bool
+js::ClampToUint8(JSContext*, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(args[0].isNumber());
+ args.rval().setNumber(ClampDoubleToUint8(args[0].toNumber()));
+ return true;
+}
+
+bool
+js::GetTypedObjectModule(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Rooted<GlobalObject*> global(cx, cx->global());
+ MOZ_ASSERT(global);
+ args.rval().setObject(global->getTypedObjectModule());
+ return true;
+}
+
+bool
+js::GetSimdTypeDescr(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(args[0].isInt32());
+ // One of the JS_SIMDTYPEREPR_* constants / a SimdType enum value.
+ // getOrCreateSimdTypeDescr() will do the range check.
+ int32_t simdTypeRepr = args[0].toInt32();
+ Rooted<GlobalObject*> global(cx, cx->global());
+ MOZ_ASSERT(global);
+ auto* obj = GlobalObject::getOrCreateSimdTypeDescr(cx, global, SimdType(simdTypeRepr));
+ args.rval().setObject(*obj);
+ return true;
+}
+
+#define JS_STORE_SCALAR_CLASS_IMPL(_constant, T, _name) \
+bool \
+js::StoreScalar##T::Func(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ CallArgs args = CallArgsFromVp(argc, vp); \
+ MOZ_ASSERT(args.length() == 3); \
+ MOZ_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>()); \
+ MOZ_ASSERT(args[1].isInt32()); \
+ MOZ_ASSERT(args[2].isNumber()); \
+ \
+ TypedObject& typedObj = args[0].toObject().as<TypedObject>(); \
+ int32_t offset = args[1].toInt32(); \
+ \
+ /* Should be guaranteed by the typed objects API: */ \
+ MOZ_ASSERT(offset % MOZ_ALIGNOF(T) == 0); \
+ \
+ JS::AutoCheckCannotGC nogc(cx); \
+ T* target = reinterpret_cast<T*>(typedObj.typedMem(offset, nogc)); \
+ double d = args[2].toNumber(); \
+ *target = ConvertScalar<T>(d); \
+ args.rval().setUndefined(); \
+ return true; \
+}
+
+#define JS_STORE_REFERENCE_CLASS_IMPL(_constant, T, _name) \
+bool \
+js::StoreReference##_name::Func(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ CallArgs args = CallArgsFromVp(argc, vp); \
+ MOZ_ASSERT(args.length() == 4); \
+ MOZ_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>()); \
+ MOZ_ASSERT(args[1].isInt32()); \
+ MOZ_ASSERT(args[2].isString() || args[2].isNull()); \
+ \
+ TypedObject& typedObj = args[0].toObject().as<TypedObject>(); \
+ int32_t offset = args[1].toInt32(); \
+ \
+ jsid id = args[2].isString() \
+ ? IdToTypeId(AtomToId(&args[2].toString()->asAtom())) \
+ : JSID_VOID; \
+ \
+ /* Should be guaranteed by the typed objects API: */ \
+ MOZ_ASSERT(offset % MOZ_ALIGNOF(T) == 0); \
+ \
+ JS::AutoCheckCannotGC nogc(cx); \
+ T* target = reinterpret_cast<T*>(typedObj.typedMem(offset, nogc)); \
+ if (!store(cx, target, args[3], &typedObj, id)) \
+ return false; \
+ args.rval().setUndefined(); \
+ return true; \
+}
+
+#define JS_LOAD_SCALAR_CLASS_IMPL(_constant, T, _name) \
+bool \
+js::LoadScalar##T::Func(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ CallArgs args = CallArgsFromVp(argc, vp); \
+ MOZ_ASSERT(args.length() == 2); \
+ MOZ_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>()); \
+ MOZ_ASSERT(args[1].isInt32()); \
+ \
+ TypedObject& typedObj = args[0].toObject().as<TypedObject>(); \
+ int32_t offset = args[1].toInt32(); \
+ \
+ /* Should be guaranteed by the typed objects API: */ \
+ MOZ_ASSERT(offset % MOZ_ALIGNOF(T) == 0); \
+ \
+ JS::AutoCheckCannotGC nogc(cx); \
+ T* target = reinterpret_cast<T*>(typedObj.typedMem(offset, nogc)); \
+ args.rval().setNumber((double) *target); \
+ return true; \
+}
+
+#define JS_LOAD_REFERENCE_CLASS_IMPL(_constant, T, _name) \
+bool \
+js::LoadReference##_name::Func(JSContext* cx, unsigned argc, Value* vp) \
+{ \
+ CallArgs args = CallArgsFromVp(argc, vp); \
+ MOZ_ASSERT(args.length() == 2); \
+ MOZ_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>()); \
+ MOZ_ASSERT(args[1].isInt32()); \
+ \
+ TypedObject& typedObj = args[0].toObject().as<TypedObject>(); \
+ int32_t offset = args[1].toInt32(); \
+ \
+ /* Should be guaranteed by the typed objects API: */ \
+ MOZ_ASSERT(offset % MOZ_ALIGNOF(T) == 0); \
+ \
+ JS::AutoCheckCannotGC nogc(cx); \
+ T* target = reinterpret_cast<T*>(typedObj.typedMem(offset, nogc)); \
+ load(target, args.rval()); \
+ return true; \
+}
+
+// Because the precise syntax for storing values/objects/strings
+// differs, we abstract it away using specialized variants of the
+// private methods `store()` and `load()`.
+
+bool
+StoreReferenceAny::store(JSContext* cx, GCPtrValue* heap, const Value& v,
+ TypedObject* obj, jsid id)
+{
+ // Undefined values are not included in type inference information for
+ // value properties of typed objects, as these properties are always
+ // considered to contain undefined.
+ if (!v.isUndefined()) {
+ if (cx->isJSContext())
+ AddTypePropertyId(cx->asJSContext(), obj, id, v);
+ else if (!HasTypePropertyId(obj, id, v))
+ return false;
+ }
+
+ *heap = v;
+ return true;
+}
+
+bool
+StoreReferenceObject::store(JSContext* cx, GCPtrObject* heap, const Value& v,
+ TypedObject* obj, jsid id)
+{
+ MOZ_ASSERT(v.isObjectOrNull()); // or else Store_object is being misused
+
+ // Null pointers are not included in type inference information for
+ // object properties of typed objects, as these properties are always
+ // considered to contain null.
+ if (v.isObject()) {
+ if (cx->isJSContext())
+ AddTypePropertyId(cx->asJSContext(), obj, id, v);
+ else if (!HasTypePropertyId(obj, id, v))
+ return false;
+ }
+
+ *heap = v.toObjectOrNull();
+ return true;
+}
+
+bool
+StoreReferencestring::store(JSContext* cx, GCPtrString* heap, const Value& v,
+ TypedObject* obj, jsid id)
+{
+ MOZ_ASSERT(v.isString()); // or else Store_string is being misused
+
+ // Note: string references are not reflected in type information for the object.
+ *heap = v.toString();
+
+ return true;
+}
+
+void
+LoadReferenceAny::load(GCPtrValue* heap, MutableHandleValue v)
+{
+ v.set(*heap);
+}
+
+void
+LoadReferenceObject::load(GCPtrObject* heap, MutableHandleValue v)
+{
+ if (*heap)
+ v.setObject(**heap);
+ else
+ v.setNull();
+}
+
+void
+LoadReferencestring::load(GCPtrString* heap, MutableHandleValue v)
+{
+ v.setString(*heap);
+}
+
+// I was using templates for this stuff instead of macros, but ran
+// into problems with the Unagi compiler.
+JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(JS_STORE_SCALAR_CLASS_IMPL)
+JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(JS_LOAD_SCALAR_CLASS_IMPL)
+JS_FOR_EACH_REFERENCE_TYPE_REPR(JS_STORE_REFERENCE_CLASS_IMPL)
+JS_FOR_EACH_REFERENCE_TYPE_REPR(JS_LOAD_REFERENCE_CLASS_IMPL)
+
+///////////////////////////////////////////////////////////////////////////
+// Walking memory
+
+template<typename V>
+static void
+visitReferences(TypeDescr& descr,
+ uint8_t* mem,
+ V& visitor)
+{
+ if (descr.transparent())
+ return;
+
+ switch (descr.kind()) {
+ case type::Scalar:
+ case type::Simd:
+ return;
+
+ case type::Reference:
+ visitor.visitReference(descr.as<ReferenceTypeDescr>(), mem);
+ return;
+
+ case type::Array:
+ {
+ ArrayTypeDescr& arrayDescr = descr.as<ArrayTypeDescr>();
+ TypeDescr& elementDescr = arrayDescr.elementType();
+ for (uint32_t i = 0; i < arrayDescr.length(); i++) {
+ visitReferences(elementDescr, mem, visitor);
+ mem += elementDescr.size();
+ }
+ return;
+ }
+
+ case type::Struct:
+ {
+ StructTypeDescr& structDescr = descr.as<StructTypeDescr>();
+ for (size_t i = 0; i < structDescr.fieldCount(); i++) {
+ TypeDescr& descr = structDescr.fieldDescr(i);
+ size_t offset = structDescr.fieldOffset(i);
+ visitReferences(descr, mem + offset, visitor);
+ }
+ return;
+ }
+ }
+
+ MOZ_CRASH("Invalid type repr kind");
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Initializing instances
+
+namespace {
+
+class MemoryInitVisitor {
+ const JSRuntime* rt_;
+
+ public:
+ explicit MemoryInitVisitor(const JSRuntime* rt)
+ : rt_(rt)
+ {}
+
+ void visitReference(ReferenceTypeDescr& descr, uint8_t* mem);
+};
+
+} // namespace
+
+void
+MemoryInitVisitor::visitReference(ReferenceTypeDescr& descr, uint8_t* mem)
+{
+ switch (descr.type()) {
+ case ReferenceTypeDescr::TYPE_ANY:
+ {
+ js::GCPtrValue* heapValue = reinterpret_cast<js::GCPtrValue*>(mem);
+ heapValue->init(UndefinedValue());
+ return;
+ }
+
+ case ReferenceTypeDescr::TYPE_OBJECT:
+ {
+ js::GCPtrObject* objectPtr =
+ reinterpret_cast<js::GCPtrObject*>(mem);
+ objectPtr->init(nullptr);
+ return;
+ }
+
+ case ReferenceTypeDescr::TYPE_STRING:
+ {
+ js::GCPtrString* stringPtr =
+ reinterpret_cast<js::GCPtrString*>(mem);
+ stringPtr->init(rt_->emptyString);
+ return;
+ }
+ }
+
+ MOZ_CRASH("Invalid kind");
+}
+
+void
+TypeDescr::initInstances(const JSRuntime* rt, uint8_t* mem, size_t length)
+{
+ MOZ_ASSERT(length >= 1);
+
+ MemoryInitVisitor visitor(rt);
+
+ // Initialize the 0th instance
+ memset(mem, 0, size());
+ if (opaque())
+ visitReferences(*this, mem, visitor);
+
+ // Stamp out N copies of later instances
+ uint8_t* target = mem;
+ for (size_t i = 1; i < length; i++) {
+ target += size();
+ memcpy(target, mem, size());
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Tracing instances
+
+namespace {
+
+class MemoryTracingVisitor {
+ JSTracer* trace_;
+
+ public:
+
+ explicit MemoryTracingVisitor(JSTracer* trace)
+ : trace_(trace)
+ {}
+
+ void visitReference(ReferenceTypeDescr& descr, uint8_t* mem);
+};
+
+} // namespace
+
+void
+MemoryTracingVisitor::visitReference(ReferenceTypeDescr& descr, uint8_t* mem)
+{
+ switch (descr.type()) {
+ case ReferenceTypeDescr::TYPE_ANY:
+ {
+ GCPtrValue* heapValue = reinterpret_cast<js::GCPtrValue*>(mem);
+ TraceEdge(trace_, heapValue, "reference-val");
+ return;
+ }
+
+ case ReferenceTypeDescr::TYPE_OBJECT:
+ {
+ GCPtrObject* objectPtr = reinterpret_cast<js::GCPtrObject*>(mem);
+ TraceNullableEdge(trace_, objectPtr, "reference-obj");
+ return;
+ }
+
+ case ReferenceTypeDescr::TYPE_STRING:
+ {
+ GCPtrString* stringPtr = reinterpret_cast<js::GCPtrString*>(mem);
+ TraceNullableEdge(trace_, stringPtr, "reference-str");
+ return;
+ }
+ }
+
+ MOZ_CRASH("Invalid kind");
+}
+
+void
+TypeDescr::traceInstances(JSTracer* trace, uint8_t* mem, size_t length)
+{
+ MemoryTracingVisitor visitor(trace);
+
+ for (size_t i = 0; i < length; i++) {
+ visitReferences(*this, mem, visitor);
+ mem += size();
+ }
+}
+
+namespace {
+
+struct TraceListVisitor {
+ typedef Vector<int32_t, 0, SystemAllocPolicy> VectorType;
+ VectorType stringOffsets, objectOffsets, valueOffsets;
+
+ void visitReference(ReferenceTypeDescr& descr, uint8_t* mem);
+
+ bool fillList(Vector<int32_t>& entries);
+};
+
+} // namespace
+
+void
+TraceListVisitor::visitReference(ReferenceTypeDescr& descr, uint8_t* mem)
+{
+ VectorType* offsets;
+ switch (descr.type()) {
+ case ReferenceTypeDescr::TYPE_ANY: offsets = &valueOffsets; break;
+ case ReferenceTypeDescr::TYPE_OBJECT: offsets = &objectOffsets; break;
+ case ReferenceTypeDescr::TYPE_STRING: offsets = &stringOffsets; break;
+ default: MOZ_CRASH("Invalid kind");
+ }
+
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (!offsets->append((uintptr_t) mem))
+ oomUnsafe.crash("TraceListVisitor::visitReference");
+}
+
+bool
+TraceListVisitor::fillList(Vector<int32_t>& entries)
+{
+ return entries.appendAll(stringOffsets) &&
+ entries.append(-1) &&
+ entries.appendAll(objectOffsets) &&
+ entries.append(-1) &&
+ entries.appendAll(valueOffsets) &&
+ entries.append(-1);
+}
+
+static bool
+CreateTraceList(JSContext* cx, HandleTypeDescr descr)
+{
+ // Trace lists are only used for inline typed objects. We don't use them
+ // for larger objects, both to limit the size of the trace lists and
+ // because tracing outline typed objects is considerably more complicated
+ // than inline ones.
+ if (descr->size() > InlineTypedObject::MaximumSize || descr->transparent())
+ return true;
+
+ TraceListVisitor visitor;
+ visitReferences(*descr, nullptr, visitor);
+
+ Vector<int32_t> entries(cx);
+ if (!visitor.fillList(entries))
+ return false;
+
+ // Trace lists aren't necessary for descriptors with no references.
+ MOZ_ASSERT(entries.length() >= 3);
+ if (entries.length() == 3)
+ return true;
+
+ int32_t* list = cx->pod_malloc<int32_t>(entries.length());
+ if (!list)
+ return false;
+
+ PodCopy(list, entries.begin(), entries.length());
+
+ descr->initReservedSlot(JS_DESCR_SLOT_TRACE_LIST, PrivateValue(list));
+ return true;
+}
+
+/* static */ void
+TypeDescr::finalize(FreeOp* fop, JSObject* obj)
+{
+ TypeDescr& descr = obj->as<TypeDescr>();
+ if (descr.hasTraceList())
+ js_free(const_cast<int32_t*>(descr.traceList()));
+}
diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h
new file mode 100644
index 000000000..6da62b656
--- /dev/null
+++ b/js/src/builtin/TypedObject.h
@@ -0,0 +1,1072 @@
+/* -*- 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/. */
+
+#ifndef builtin_TypedObject_h
+#define builtin_TypedObject_h
+
+#include "jsobj.h"
+#include "jsweakmap.h"
+
+#include "builtin/TypedObjectConstants.h"
+#include "js/Conversions.h"
+#include "vm/ArrayBufferObject.h"
+#include "vm/ShapedObject.h"
+
+/*
+ * -------------
+ * Typed Objects
+ * -------------
+ *
+ * Typed objects are a special kind of JS object where the data is
+ * given well-structured form. To use a typed object, users first
+ * create *type objects* (no relation to the type objects used in TI)
+ * that define the type layout. For example, a statement like:
+ *
+ * var PointType = new StructType({x: uint8, y: uint8});
+ *
+ * would create a type object PointType that is a struct with
+ * two fields, each of uint8 type.
+ *
+ * This comment typically assumes familiary with the API. For more
+ * info on the API itself, see the Harmony wiki page at
+ * http://wiki.ecmascript.org/doku.php?id=harmony:typed_objects or the
+ * ES6 spec (not finalized at the time of this writing).
+ *
+ * - Initialization:
+ *
+ * Currently, all "globals" related to typed objects are packaged
+ * within a single "module" object `TypedObject`. This module has its
+ * own js::Class and when that class is initialized, we also create
+ * and define all other values (in `js::InitTypedObjectModuleClass()`).
+ *
+ * - Type objects, meta type objects, and type representations:
+ *
+ * There are a number of pre-defined type objects, one for each
+ * scalar type (`uint8` etc). Each of these has its own class_,
+ * defined in `DefineNumericClass()`.
+ *
+ * There are also meta type objects (`ArrayType`, `StructType`).
+ * These constructors are not themselves type objects but rather the
+ * means for the *user* to construct new typed objects.
+ *
+ * Each type object is associated with a *type representation* (see
+ * TypeRepresentation.h). Type representations are canonical versions
+ * of type objects. We attach them to TI type objects and (eventually)
+ * use them for shape guards etc. They are purely internal to the
+ * engine and are not exposed to end users (though self-hosted code
+ * sometimes accesses them).
+ *
+ * - Typed objects:
+ *
+ * A typed object is an instance of a *type object* (note the past participle).
+ * Typed objects can be either transparent or opaque, depending on whether
+ * their underlying buffer can be accessed. Transparent and opaque typed
+ * objects have different classes, and can have different physical layouts.
+ * The following layouts are possible:
+ *
+ * InlineTypedObject: Typed objects whose data immediately follows the object's
+ * header are inline typed objects. The buffer for these objects is created
+ * lazily and stored via the compartment's LazyArrayBufferTable, and points
+ * back into the object's internal data.
+ *
+ * OutlineTypedObject: Typed objects whose data is owned by another object,
+ * which can be either an array buffer or an inline typed object. Outline
+ * typed objects may be attached or unattached. An unattached typed object
+ * has no data associated with it. When first created, objects are always
+ * attached, but they can become unattached if their buffer becomes detached.
+ *
+ * Note that whether a typed object is opaque is not directly
+ * connected to its type. That is, opaque types are *always*
+ * represented by opaque typed objects, but you may have opaque typed
+ * objects for transparent types too. This can occur for two reasons:
+ * (1) a transparent type may be embedded within an opaque type or (2)
+ * users can choose to convert transparent typed objects into opaque
+ * ones to avoid giving access to the buffer itself.
+ *
+ * Typed objects (no matter their class) are non-native objects that
+ * fully override the property accessors etc. The overridden accessor
+ * methods are the same in each and are defined in methods of
+ * TypedObject.
+ */
+
+namespace js {
+
+/*
+ * Helper method for converting a double into other scalar
+ * types in the same way that JavaScript would. In particular,
+ * simple C casting from double to int32_t gets things wrong
+ * for values like 0xF0000000.
+ */
+template <typename T>
+static T ConvertScalar(double d)
+{
+ if (TypeIsFloatingPoint<T>())
+ return T(d);
+ if (TypeIsUnsigned<T>()) {
+ uint32_t n = JS::ToUint32(d);
+ return T(n);
+ }
+ int32_t n = JS::ToInt32(d);
+ return T(n);
+}
+
+namespace type {
+
+enum Kind {
+ Scalar = JS_TYPEREPR_SCALAR_KIND,
+ Reference = JS_TYPEREPR_REFERENCE_KIND,
+ Simd = JS_TYPEREPR_SIMD_KIND,
+ Struct = JS_TYPEREPR_STRUCT_KIND,
+ Array = JS_TYPEREPR_ARRAY_KIND
+};
+
+} // namespace type
+
+///////////////////////////////////////////////////////////////////////////
+// Typed Prototypes
+
+class SimpleTypeDescr;
+class ComplexTypeDescr;
+class SimdTypeDescr;
+class StructTypeDescr;
+class TypedProto;
+
+/*
+ * The prototype for a typed object.
+ */
+class TypedProto : public NativeObject
+{
+ public:
+ static const Class class_;
+};
+
+class TypeDescr : public NativeObject
+{
+ public:
+ TypedProto& typedProto() const {
+ return getReservedSlot(JS_DESCR_SLOT_TYPROTO).toObject().as<TypedProto>();
+ }
+
+ JSAtom& stringRepr() const {
+ return getReservedSlot(JS_DESCR_SLOT_STRING_REPR).toString()->asAtom();
+ }
+
+ type::Kind kind() const {
+ return (type::Kind) getReservedSlot(JS_DESCR_SLOT_KIND).toInt32();
+ }
+
+ bool opaque() const {
+ return getReservedSlot(JS_DESCR_SLOT_OPAQUE).toBoolean();
+ }
+
+ bool transparent() const {
+ return !opaque();
+ }
+
+ uint32_t alignment() const {
+ int32_t i = getReservedSlot(JS_DESCR_SLOT_ALIGNMENT).toInt32();
+ MOZ_ASSERT(i >= 0);
+ return uint32_t(i);
+ }
+
+ uint32_t size() const {
+ int32_t i = getReservedSlot(JS_DESCR_SLOT_SIZE).toInt32();
+ MOZ_ASSERT(i >= 0);
+ return uint32_t(i);
+ }
+
+ // Whether id is an 'own' property of objects with this descriptor.
+ MOZ_MUST_USE bool hasProperty(const JSAtomState& names, jsid id);
+
+ // Type descriptors may contain a list of their references for use during
+ // scanning. Marking code is optimized to use this list to mark inline
+ // typed objects, rather than the slower trace hook. This list is only
+ // specified when (a) the descriptor is short enough that it can fit in an
+ // InlineTypedObject, and (b) the descriptor contains at least one
+ // reference. Otherwise its value is undefined.
+ //
+ // The list is three consecutive arrays of int32_t offsets, with each array
+ // terminated by -1. The arrays store offsets of string, object, and value
+ // references in the descriptor, in that order.
+ MOZ_MUST_USE bool hasTraceList() const {
+ return !getFixedSlot(JS_DESCR_SLOT_TRACE_LIST).isUndefined();
+ }
+ const int32_t* traceList() const {
+ MOZ_ASSERT(hasTraceList());
+ return reinterpret_cast<int32_t*>(getFixedSlot(JS_DESCR_SLOT_TRACE_LIST).toPrivate());
+ }
+
+ void initInstances(const JSRuntime* rt, uint8_t* mem, size_t length);
+ void traceInstances(JSTracer* trace, uint8_t* mem, size_t length);
+
+ static void finalize(FreeOp* fop, JSObject* obj);
+};
+
+typedef Handle<TypeDescr*> HandleTypeDescr;
+
+class SimpleTypeDescr : public TypeDescr
+{
+};
+
+// Type for scalar type constructors like `uint8`. All such type
+// constructors share a common js::Class and JSFunctionSpec. Scalar
+// types are non-opaque (their storage is visible unless combined with
+// an opaque reference type.)
+class ScalarTypeDescr : public SimpleTypeDescr
+{
+ public:
+ typedef Scalar::Type Type;
+
+ static const type::Kind Kind = type::Scalar;
+ static const bool Opaque = false;
+ static uint32_t size(Type t);
+ static uint32_t alignment(Type t);
+ static const char* typeName(Type type);
+
+ static const Class class_;
+ static const JSFunctionSpec typeObjectMethods[];
+
+ Type type() const {
+ // Make sure the values baked into TypedObjectConstants.h line up with
+ // the Scalar::Type enum. We don't define Scalar::Type directly in
+ // terms of these constants to avoid making TypedObjectConstants.h a
+ // public header file.
+ static_assert(Scalar::Int8 == JS_SCALARTYPEREPR_INT8,
+ "TypedObjectConstants.h must be consistent with Scalar::Type");
+ static_assert(Scalar::Uint8 == JS_SCALARTYPEREPR_UINT8,
+ "TypedObjectConstants.h must be consistent with Scalar::Type");
+ static_assert(Scalar::Int16 == JS_SCALARTYPEREPR_INT16,
+ "TypedObjectConstants.h must be consistent with Scalar::Type");
+ static_assert(Scalar::Uint16 == JS_SCALARTYPEREPR_UINT16,
+ "TypedObjectConstants.h must be consistent with Scalar::Type");
+ static_assert(Scalar::Int32 == JS_SCALARTYPEREPR_INT32,
+ "TypedObjectConstants.h must be consistent with Scalar::Type");
+ static_assert(Scalar::Uint32 == JS_SCALARTYPEREPR_UINT32,
+ "TypedObjectConstants.h must be consistent with Scalar::Type");
+ static_assert(Scalar::Float32 == JS_SCALARTYPEREPR_FLOAT32,
+ "TypedObjectConstants.h must be consistent with Scalar::Type");
+ static_assert(Scalar::Float64 == JS_SCALARTYPEREPR_FLOAT64,
+ "TypedObjectConstants.h must be consistent with Scalar::Type");
+ static_assert(Scalar::Uint8Clamped == JS_SCALARTYPEREPR_UINT8_CLAMPED,
+ "TypedObjectConstants.h must be consistent with Scalar::Type");
+ static_assert(Scalar::Float32x4 == JS_SCALARTYPEREPR_FLOAT32X4,
+ "TypedObjectConstants.h must be consistent with Scalar::Type");
+ static_assert(Scalar::Int8x16 == JS_SCALARTYPEREPR_INT8X16,
+ "TypedObjectConstants.h must be consistent with Scalar::Type");
+ static_assert(Scalar::Int16x8 == JS_SCALARTYPEREPR_INT16X8,
+ "TypedObjectConstants.h must be consistent with Scalar::Type");
+ static_assert(Scalar::Int32x4 == JS_SCALARTYPEREPR_INT32X4,
+ "TypedObjectConstants.h must be consistent with Scalar::Type");
+
+ return Type(getReservedSlot(JS_DESCR_SLOT_TYPE).toInt32());
+ }
+
+ static MOZ_MUST_USE bool call(JSContext* cx, unsigned argc, Value* vp);
+};
+
+// Enumerates the cases of ScalarTypeDescr::Type which have
+// unique C representation. In particular, omits Uint8Clamped since it
+// is just a Uint8.
+#define JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(macro_) \
+ macro_(Scalar::Int8, int8_t, int8) \
+ macro_(Scalar::Uint8, uint8_t, uint8) \
+ macro_(Scalar::Int16, int16_t, int16) \
+ macro_(Scalar::Uint16, uint16_t, uint16) \
+ macro_(Scalar::Int32, int32_t, int32) \
+ macro_(Scalar::Uint32, uint32_t, uint32) \
+ macro_(Scalar::Float32, float, float32) \
+ macro_(Scalar::Float64, double, float64)
+
+// Must be in same order as the enum ScalarTypeDescr::Type:
+#define JS_FOR_EACH_SCALAR_TYPE_REPR(macro_) \
+ JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(macro_) \
+ macro_(Scalar::Uint8Clamped, uint8_t, uint8Clamped)
+
+// Type for reference type constructors like `Any`, `String`, and
+// `Object`. All such type constructors share a common js::Class and
+// JSFunctionSpec. All these types are opaque.
+class ReferenceTypeDescr : public SimpleTypeDescr
+{
+ public:
+ // Must match order of JS_FOR_EACH_REFERENCE_TYPE_REPR below
+ enum Type {
+ TYPE_ANY = JS_REFERENCETYPEREPR_ANY,
+ TYPE_OBJECT = JS_REFERENCETYPEREPR_OBJECT,
+ TYPE_STRING = JS_REFERENCETYPEREPR_STRING,
+ };
+ static const int32_t TYPE_MAX = TYPE_STRING + 1;
+ static const char* typeName(Type type);
+
+ static const type::Kind Kind = type::Reference;
+ static const bool Opaque = true;
+ static const Class class_;
+ static uint32_t size(Type t);
+ static uint32_t alignment(Type t);
+ static const JSFunctionSpec typeObjectMethods[];
+
+ ReferenceTypeDescr::Type type() const {
+ return (ReferenceTypeDescr::Type) getReservedSlot(JS_DESCR_SLOT_TYPE).toInt32();
+ }
+
+ const char* typeName() const {
+ return typeName(type());
+ }
+
+ static MOZ_MUST_USE bool call(JSContext* cx, unsigned argc, Value* vp);
+};
+
+#define JS_FOR_EACH_REFERENCE_TYPE_REPR(macro_) \
+ macro_(ReferenceTypeDescr::TYPE_ANY, GCPtrValue, Any) \
+ macro_(ReferenceTypeDescr::TYPE_OBJECT, GCPtrObject, Object) \
+ macro_(ReferenceTypeDescr::TYPE_STRING, GCPtrString, string)
+
+// Type descriptors whose instances are objects and hence which have
+// an associated `prototype` property.
+class ComplexTypeDescr : public TypeDescr
+{
+ public:
+ // Returns the prototype that instances of this type descriptor
+ // will have.
+ TypedProto& instancePrototype() const {
+ return getReservedSlot(JS_DESCR_SLOT_TYPROTO).toObject().as<TypedProto>();
+ }
+};
+
+enum class SimdType;
+
+/*
+ * SIMD Type descriptors.
+ */
+class SimdTypeDescr : public ComplexTypeDescr
+{
+ public:
+ static const type::Kind Kind = type::Simd;
+ static const bool Opaque = false;
+ static const Class class_;
+ static uint32_t size(SimdType t);
+ static uint32_t alignment(SimdType t);
+ static MOZ_MUST_USE bool call(JSContext* cx, unsigned argc, Value* vp);
+ static bool is(const Value& v);
+
+ SimdType type() const;
+};
+
+bool IsTypedObjectClass(const Class* clasp); // Defined below
+bool IsTypedObjectArray(JSObject& obj);
+
+MOZ_MUST_USE bool CreateUserSizeAndAlignmentProperties(JSContext* cx, HandleTypeDescr obj);
+
+class ArrayTypeDescr;
+
+/*
+ * Properties and methods of the `ArrayType` meta type object. There
+ * is no `class_` field because `ArrayType` is just a native
+ * constructor function.
+ */
+class ArrayMetaTypeDescr : public NativeObject
+{
+ private:
+ // Helper for creating a new ArrayType object.
+ //
+ // - `arrayTypePrototype` - prototype for the new object to be created
+ // - `elementType` - type object for the elements in the array
+ // - `stringRepr` - canonical string representation for the array
+ // - `size` - length of the array
+ static ArrayTypeDescr* create(JSContext* cx,
+ HandleObject arrayTypePrototype,
+ HandleTypeDescr elementType,
+ HandleAtom stringRepr,
+ int32_t size,
+ int32_t length);
+
+ public:
+ // Properties and methods to be installed on ArrayType.prototype,
+ // and hence inherited by all array type objects:
+ static const JSPropertySpec typeObjectProperties[];
+ static const JSFunctionSpec typeObjectMethods[];
+
+ // Properties and methods to be installed on ArrayType.prototype.prototype,
+ // and hence inherited by all array *typed* objects:
+ static const JSPropertySpec typedObjectProperties[];
+ static const JSFunctionSpec typedObjectMethods[];
+
+ // This is the function that gets called when the user
+ // does `new ArrayType(elem)`. It produces an array type object.
+ static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
+};
+
+/*
+ * Type descriptor created by `new ArrayType(type, n)`
+ */
+class ArrayTypeDescr : public ComplexTypeDescr
+{
+ public:
+ static const Class class_;
+ static const type::Kind Kind = type::Array;
+
+ TypeDescr& elementType() const {
+ return getReservedSlot(JS_DESCR_SLOT_ARRAY_ELEM_TYPE).toObject().as<TypeDescr>();
+ }
+
+ uint32_t length() const {
+ int32_t i = getReservedSlot(JS_DESCR_SLOT_ARRAY_LENGTH).toInt32();
+ MOZ_ASSERT(i >= 0);
+ return uint32_t(i);
+ }
+
+ static int32_t offsetOfLength() {
+ return getFixedSlotOffset(JS_DESCR_SLOT_ARRAY_LENGTH);
+ }
+};
+
+/*
+ * Properties and methods of the `StructType` meta type object. There
+ * is no `class_` field because `StructType` is just a native
+ * constructor function.
+ */
+class StructMetaTypeDescr : public NativeObject
+{
+ private:
+ static JSObject* create(JSContext* cx, HandleObject structTypeGlobal,
+ HandleObject fields);
+
+ public:
+ // Properties and methods to be installed on StructType.prototype,
+ // and hence inherited by all struct type objects:
+ static const JSPropertySpec typeObjectProperties[];
+ static const JSFunctionSpec typeObjectMethods[];
+
+ // Properties and methods to be installed on StructType.prototype.prototype,
+ // and hence inherited by all struct *typed* objects:
+ static const JSPropertySpec typedObjectProperties[];
+ static const JSFunctionSpec typedObjectMethods[];
+
+ // This is the function that gets called when the user
+ // does `new StructType(...)`. It produces a struct type object.
+ static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
+};
+
+class StructTypeDescr : public ComplexTypeDescr
+{
+ public:
+ static const Class class_;
+
+ // Returns the number of fields defined in this struct.
+ size_t fieldCount() const;
+
+ // Set `*out` to the index of the field named `id` and returns true,
+ // or return false if no such field exists.
+ MOZ_MUST_USE bool fieldIndex(jsid id, size_t* out) const;
+
+ // Return the name of the field at index `index`.
+ JSAtom& fieldName(size_t index) const;
+
+ // Return the type descr of the field at index `index`.
+ TypeDescr& fieldDescr(size_t index) const;
+
+ // Return the offset of the field at index `index`.
+ size_t fieldOffset(size_t index) const;
+
+ private:
+ ArrayObject& fieldInfoObject(size_t slot) const {
+ return getReservedSlot(slot).toObject().as<ArrayObject>();
+ }
+};
+
+typedef Handle<StructTypeDescr*> HandleStructTypeDescr;
+
+/*
+ * This object exists in order to encapsulate the typed object types
+ * somewhat, rather than sticking them all into the global object.
+ * Eventually it will go away and become a module.
+ */
+class TypedObjectModuleObject : public NativeObject {
+ public:
+ enum Slot {
+ ArrayTypePrototype,
+ StructTypePrototype,
+ SlotCount
+ };
+
+ static const Class class_;
+};
+
+/* Base type for transparent and opaque typed objects. */
+class TypedObject : public ShapedObject
+{
+ static const bool IsTypedObjectClass = true;
+
+ static MOZ_MUST_USE bool obj_getArrayElement(JSContext* cx,
+ Handle<TypedObject*> typedObj,
+ Handle<TypeDescr*> typeDescr,
+ uint32_t index,
+ MutableHandleValue vp);
+
+ protected:
+ static const ObjectOps objectOps_;
+
+ static MOZ_MUST_USE bool obj_lookupProperty(JSContext* cx, HandleObject obj,
+ HandleId id, MutableHandleObject objp,
+ MutableHandleShape propp);
+
+ static MOZ_MUST_USE bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result);
+
+ static MOZ_MUST_USE bool obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id,
+ bool* foundp);
+
+ static MOZ_MUST_USE bool obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
+ HandleId id, MutableHandleValue vp);
+
+ static MOZ_MUST_USE bool obj_getElement(JSContext* cx, HandleObject obj, HandleValue receiver,
+ uint32_t index, MutableHandleValue vp);
+
+ static MOZ_MUST_USE bool obj_setProperty(JSContext* cx, HandleObject obj, HandleId id,
+ HandleValue v, HandleValue receiver,
+ ObjectOpResult& result);
+
+ static MOZ_MUST_USE bool obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj,
+ HandleId id,
+ MutableHandle<PropertyDescriptor> desc);
+
+ static MOZ_MUST_USE bool obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
+ ObjectOpResult& result);
+
+ static MOZ_MUST_USE bool obj_enumerate(JSContext* cx, HandleObject obj,
+ AutoIdVector& properties, bool enumerableOnly);
+
+
+ uint8_t* typedMem() const;
+ uint8_t* typedMemBase() const;
+
+ public:
+ TypedProto& typedProto() const {
+ // Typed objects' prototypes can't be modified.
+ return staticPrototype()->as<TypedProto>();
+ }
+
+ TypeDescr& typeDescr() const {
+ return group()->typeDescr();
+ }
+
+ uint32_t offset() const;
+ uint32_t length() const;
+ uint8_t* typedMem(const JS::AutoRequireNoGC&) const { return typedMem(); }
+ bool isAttached() const;
+
+ uint32_t size() const {
+ return typeDescr().size();
+ }
+
+ uint8_t* typedMem(size_t offset, const JS::AutoRequireNoGC& nogc) const {
+ // It seems a bit surprising that one might request an offset
+ // == size(), but it can happen when taking the "address of" a
+ // 0-sized value. (In other words, we maintain the invariant
+ // that `offset + size <= size()` -- this is always checked in
+ // the caller's side.)
+ MOZ_ASSERT(offset <= (size_t) size());
+ return typedMem(nogc) + offset;
+ }
+
+ inline MOZ_MUST_USE bool opaque() const;
+
+ // Creates a new typed object whose memory is freshly allocated and
+ // initialized with zeroes (or, in the case of references, an appropriate
+ // default value).
+ static TypedObject* createZeroed(JSContext* cx, HandleTypeDescr typeObj, int32_t length,
+ gc::InitialHeap heap = gc::DefaultHeap);
+
+ // User-accessible constructor (`new TypeDescriptor(...)`). Note that the
+ // callee here is the type descriptor.
+ static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
+
+ /* Accessors for self hosted code. */
+ static MOZ_MUST_USE bool GetBuffer(JSContext* cx, unsigned argc, Value* vp);
+ static MOZ_MUST_USE bool GetByteOffset(JSContext* cx, unsigned argc, Value* vp);
+
+ Shape** addressOfShapeFromGC() { return shape_.unsafeUnbarrieredForTracing(); }
+};
+
+typedef Handle<TypedObject*> HandleTypedObject;
+
+class OutlineTypedObject : public TypedObject
+{
+ // The object which owns the data this object points to. Because this
+ // pointer is managed in tandem with |data|, this is not a GCPtr and
+ // barriers are managed directly.
+ JSObject* owner_;
+
+ // Data pointer to some offset in the owner's contents.
+ uint8_t* data_;
+
+ void setOwnerAndData(JSObject* owner, uint8_t* data);
+
+ public:
+ // JIT accessors.
+ static size_t offsetOfData() { return offsetof(OutlineTypedObject, data_); }
+ static size_t offsetOfOwner() { return offsetof(OutlineTypedObject, owner_); }
+
+ JSObject& owner() const {
+ MOZ_ASSERT(owner_);
+ return *owner_;
+ }
+
+ JSObject* maybeOwner() const {
+ return owner_;
+ }
+
+ uint8_t* outOfLineTypedMem() const {
+ return data_;
+ }
+
+ void setData(uint8_t* data) {
+ data_ = data;
+ }
+
+ void resetOffset(size_t offset) {
+ MOZ_ASSERT(offset <= (size_t) size());
+ setData(typedMemBase() + offset);
+ }
+
+ // Helper for createUnattached()
+ static OutlineTypedObject* createUnattachedWithClass(JSContext* cx,
+ const Class* clasp,
+ HandleTypeDescr type,
+ int32_t length,
+ gc::InitialHeap heap = gc::DefaultHeap);
+
+ // Creates an unattached typed object or handle (depending on the
+ // type parameter T). Note that it is only legal for unattached
+ // handles to escape to the end user; for non-handles, the caller
+ // should always invoke one of the `attach()` methods below.
+ //
+ // Arguments:
+ // - type: type object for resulting object
+ // - length: 0 unless this is an array, otherwise the length
+ static OutlineTypedObject* createUnattached(JSContext* cx, HandleTypeDescr type,
+ int32_t length, gc::InitialHeap heap = gc::DefaultHeap);
+
+ // Creates a typedObj that aliases the memory pointed at by `owner`
+ // at the given offset. The typedObj will be a handle iff type is a
+ // handle and a typed object otherwise.
+ static OutlineTypedObject* createDerived(JSContext* cx,
+ HandleTypeDescr type,
+ Handle<TypedObject*> typedContents,
+ uint32_t offset);
+
+ // Use this method when `buffer` is the owner of the memory.
+ void attach(JSContext* cx, ArrayBufferObject& buffer, uint32_t offset);
+
+ // Otherwise, use this to attach to memory referenced by another typedObj.
+ void attach(JSContext* cx, TypedObject& typedObj, uint32_t offset);
+
+ // Invoked when array buffer is transferred elsewhere
+ void notifyBufferDetached(void* newData);
+
+ static void obj_trace(JSTracer* trace, JSObject* object);
+};
+
+// Class for a transparent typed object whose owner is an array buffer.
+class OutlineTransparentTypedObject : public OutlineTypedObject
+{
+ public:
+ static const Class class_;
+
+ ArrayBufferObject* getOrCreateBuffer(JSContext* cx);
+};
+
+// Class for an opaque typed object whose owner may be either an array buffer
+// or an opaque inlined typed object.
+class OutlineOpaqueTypedObject : public OutlineTypedObject
+{
+ public:
+ static const Class class_;
+};
+
+// Class for a typed object whose data is allocated inline.
+class InlineTypedObject : public TypedObject
+{
+ friend class TypedObject;
+
+ // Start of the inline data, which immediately follows the shape and type.
+ uint8_t data_[1];
+
+ protected:
+ uint8_t* inlineTypedMem() const {
+ return (uint8_t*) &data_;
+ }
+
+ public:
+ static const size_t MaximumSize = JSObject::MAX_BYTE_SIZE - sizeof(TypedObject);
+
+ static gc::AllocKind allocKindForTypeDescriptor(TypeDescr* descr) {
+ size_t nbytes = descr->size();
+ MOZ_ASSERT(nbytes <= MaximumSize);
+
+ return gc::GetGCObjectKindForBytes(nbytes + sizeof(TypedObject));
+ }
+
+ uint8_t* inlineTypedMem(const JS::AutoRequireNoGC&) const {
+ return inlineTypedMem();
+ }
+
+ uint8_t* inlineTypedMemForGC() const {
+ return inlineTypedMem();
+ }
+
+ static void obj_trace(JSTracer* trace, JSObject* object);
+ static void objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src);
+
+ static size_t offsetOfDataStart() {
+ return offsetof(InlineTypedObject, data_);
+ }
+
+ static InlineTypedObject* create(JSContext* cx, HandleTypeDescr descr,
+ gc::InitialHeap heap = gc::DefaultHeap);
+ static InlineTypedObject* createCopy(JSContext* cx, Handle<InlineTypedObject*> templateObject,
+ gc::InitialHeap heap);
+};
+
+// Class for a transparent typed object with inline data, which may have a
+// lazily allocated array buffer.
+class InlineTransparentTypedObject : public InlineTypedObject
+{
+ public:
+ static const Class class_;
+
+ ArrayBufferObject* getOrCreateBuffer(JSContext* cx);
+
+ uint8_t* inlineTypedMem() const {
+ return InlineTypedObject::inlineTypedMem();
+ }
+};
+
+// Class for an opaque typed object with inline data and no array buffer.
+class InlineOpaqueTypedObject : public InlineTypedObject
+{
+ public:
+ static const Class class_;
+};
+
+// Class for the global SIMD object.
+class SimdObject : public JSObject
+{
+ public:
+ static const Class class_;
+ static MOZ_MUST_USE bool toString(JSContext* cx, unsigned int argc, Value* vp);
+ static MOZ_MUST_USE bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId,
+ bool* resolved);
+};
+
+/*
+ * Usage: NewOpaqueTypedObject(typeObj)
+ *
+ * Constructs a new, unattached instance of `Handle`.
+ */
+MOZ_MUST_USE bool NewOpaqueTypedObject(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Usage: NewDerivedTypedObject(typeObj, owner, offset)
+ *
+ * Constructs a new, unattached instance of `Handle`.
+ */
+MOZ_MUST_USE bool NewDerivedTypedObject(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Usage: AttachTypedObject(typedObj, newDatum, newOffset)
+ *
+ * Moves `typedObj` to point at the memory referenced by `newDatum` with
+ * the offset `newOffset`.
+ */
+MOZ_MUST_USE bool AttachTypedObject(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Usage: SetTypedObjectOffset(typedObj, offset)
+ *
+ * Changes the offset for `typedObj` within its buffer to `offset`.
+ * `typedObj` must already be attached.
+ */
+MOZ_MUST_USE bool SetTypedObjectOffset(JSContext*, unsigned argc, Value* vp);
+
+/*
+ * Usage: ObjectIsTypeDescr(obj)
+ *
+ * True if `obj` is a type object.
+ */
+MOZ_MUST_USE bool ObjectIsTypeDescr(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Usage: ObjectIsTypedObject(obj)
+ *
+ * True if `obj` is a transparent or opaque typed object.
+ */
+MOZ_MUST_USE bool ObjectIsTypedObject(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Usage: ObjectIsOpaqueTypedObject(obj)
+ *
+ * True if `obj` is an opaque typed object.
+ */
+MOZ_MUST_USE bool ObjectIsOpaqueTypedObject(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Usage: ObjectIsTransparentTypedObject(obj)
+ *
+ * True if `obj` is a transparent typed object.
+ */
+MOZ_MUST_USE bool ObjectIsTransparentTypedObject(JSContext* cx, unsigned argc, Value* vp);
+
+/* Predicates on type descriptor objects. In all cases, 'obj' must be a type descriptor. */
+
+MOZ_MUST_USE bool TypeDescrIsSimpleType(JSContext*, unsigned argc, Value* vp);
+
+MOZ_MUST_USE bool TypeDescrIsArrayType(JSContext*, unsigned argc, Value* vp);
+
+/*
+ * Usage: TypedObjectIsAttached(obj)
+ *
+ * Given a TypedObject `obj`, returns true if `obj` is
+ * "attached" (i.e., its data pointer is nullptr).
+ */
+MOZ_MUST_USE bool TypedObjectIsAttached(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Usage: TypedObjectTypeDescr(obj)
+ *
+ * Given a TypedObject `obj`, returns the object's type descriptor.
+ */
+MOZ_MUST_USE bool TypedObjectTypeDescr(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Usage: ClampToUint8(v)
+ *
+ * Same as the C function ClampDoubleToUint8. `v` must be a number.
+ */
+MOZ_MUST_USE bool ClampToUint8(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Usage: GetTypedObjectModule()
+ *
+ * Returns the global "typed object" module, which provides access
+ * to the various builtin type descriptors. These are currently
+ * exported as immutable properties so it is safe for self-hosted code
+ * to access them; eventually this should be linked into the module
+ * system.
+ */
+MOZ_MUST_USE bool GetTypedObjectModule(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Usage: GetSimdTypeDescr(simdTypeRepr)
+ *
+ * Returns one of the SIMD type objects, identified by `simdTypeRepr` which must
+ * be one of the JS_SIMDTYPEREPR_* constants.
+ *
+ * The SIMD pseudo-module must have been initialized for this to be safe.
+ */
+MOZ_MUST_USE bool GetSimdTypeDescr(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Usage: Store_int8(targetDatum, targetOffset, value)
+ * ...
+ * Store_uint8(targetDatum, targetOffset, value)
+ * ...
+ * Store_float32(targetDatum, targetOffset, value)
+ * Store_float64(targetDatum, targetOffset, value)
+ *
+ * Intrinsic function. Stores `value` into the memory referenced by
+ * `targetDatum` at the offset `targetOffset`.
+ *
+ * Assumes (and asserts) that:
+ * - `targetDatum` is attached
+ * - `targetOffset` is a valid offset within the bounds of `targetDatum`
+ * - `value` is a number
+ */
+#define JS_STORE_SCALAR_CLASS_DEFN(_constant, T, _name) \
+class StoreScalar##T { \
+ public: \
+ static MOZ_MUST_USE bool Func(JSContext* cx, unsigned argc, Value* vp); \
+ static const JSJitInfo JitInfo; \
+};
+
+/*
+ * Usage: Store_Any(targetDatum, targetOffset, fieldName, value)
+ * Store_Object(targetDatum, targetOffset, fieldName, value)
+ * Store_string(targetDatum, targetOffset, fieldName, value)
+ *
+ * Intrinsic function. Stores `value` into the memory referenced by
+ * `targetDatum` at the offset `targetOffset`.
+ *
+ * Assumes (and asserts) that:
+ * - `targetDatum` is attached
+ * - `targetOffset` is a valid offset within the bounds of `targetDatum`
+ * - `value` is an object or null (`Store_Object`) or string (`Store_string`).
+ */
+#define JS_STORE_REFERENCE_CLASS_DEFN(_constant, T, _name) \
+class StoreReference##_name { \
+ private: \
+ static MOZ_MUST_USE bool store(JSContext* cx, T* heap, const Value& v, \
+ TypedObject* obj, jsid id); \
+ \
+ public: \
+ static MOZ_MUST_USE bool Func(JSContext* cx, unsigned argc, Value* vp); \
+ static const JSJitInfo JitInfo; \
+};
+
+/*
+ * Usage: LoadScalar(targetDatum, targetOffset, value)
+ *
+ * Intrinsic function. Loads value (which must be an int32 or uint32)
+ * by `scalarTypeRepr` (which must be a type repr obj) and loads the
+ * value at the memory for `targetDatum` at offset `targetOffset`.
+ * `targetDatum` must be attached.
+ */
+#define JS_LOAD_SCALAR_CLASS_DEFN(_constant, T, _name) \
+class LoadScalar##T { \
+ public: \
+ static MOZ_MUST_USE bool Func(JSContext* cx, unsigned argc, Value* vp); \
+ static const JSJitInfo JitInfo; \
+};
+
+/*
+ * Usage: LoadReference(targetDatum, targetOffset, value)
+ *
+ * Intrinsic function. Stores value (which must be an int32 or uint32)
+ * by `scalarTypeRepr` (which must be a type repr obj) and stores the
+ * value at the memory for `targetDatum` at offset `targetOffset`.
+ * `targetDatum` must be attached.
+ */
+#define JS_LOAD_REFERENCE_CLASS_DEFN(_constant, T, _name) \
+class LoadReference##_name { \
+ private: \
+ static void load(T* heap, MutableHandleValue v); \
+ \
+ public: \
+ static MOZ_MUST_USE bool Func(JSContext* cx, unsigned argc, Value* vp); \
+ static const JSJitInfo JitInfo; \
+};
+
+// I was using templates for this stuff instead of macros, but ran
+// into problems with the Unagi compiler.
+JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(JS_STORE_SCALAR_CLASS_DEFN)
+JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(JS_LOAD_SCALAR_CLASS_DEFN)
+JS_FOR_EACH_REFERENCE_TYPE_REPR(JS_STORE_REFERENCE_CLASS_DEFN)
+JS_FOR_EACH_REFERENCE_TYPE_REPR(JS_LOAD_REFERENCE_CLASS_DEFN)
+
+inline bool
+IsTypedObjectClass(const Class* class_)
+{
+ return class_ == &OutlineTransparentTypedObject::class_ ||
+ class_ == &InlineTransparentTypedObject::class_ ||
+ class_ == &OutlineOpaqueTypedObject::class_ ||
+ class_ == &InlineOpaqueTypedObject::class_;
+}
+
+inline bool
+IsOpaqueTypedObjectClass(const Class* class_)
+{
+ return class_ == &OutlineOpaqueTypedObject::class_ ||
+ class_ == &InlineOpaqueTypedObject::class_;
+}
+
+inline bool
+IsOutlineTypedObjectClass(const Class* class_)
+{
+ return class_ == &OutlineOpaqueTypedObject::class_ ||
+ class_ == &OutlineTransparentTypedObject::class_;
+}
+
+inline bool
+IsInlineTypedObjectClass(const Class* class_)
+{
+ return class_ == &InlineOpaqueTypedObject::class_ ||
+ class_ == &InlineTransparentTypedObject::class_;
+}
+
+inline const Class*
+GetOutlineTypedObjectClass(bool opaque)
+{
+ return opaque ? &OutlineOpaqueTypedObject::class_ : &OutlineTransparentTypedObject::class_;
+}
+
+inline bool
+IsSimpleTypeDescrClass(const Class* clasp)
+{
+ return clasp == &ScalarTypeDescr::class_ ||
+ clasp == &ReferenceTypeDescr::class_;
+}
+
+inline bool
+IsComplexTypeDescrClass(const Class* clasp)
+{
+ return clasp == &StructTypeDescr::class_ ||
+ clasp == &ArrayTypeDescr::class_ ||
+ clasp == &SimdTypeDescr::class_;
+}
+
+inline bool
+IsTypeDescrClass(const Class* clasp)
+{
+ return IsSimpleTypeDescrClass(clasp) ||
+ IsComplexTypeDescrClass(clasp);
+}
+
+inline bool
+TypedObject::opaque() const
+{
+ return IsOpaqueTypedObjectClass(getClass());
+}
+
+JSObject*
+InitTypedObjectModuleObject(JSContext* cx, JS::HandleObject obj);
+
+} // namespace js
+
+template <>
+inline bool
+JSObject::is<js::SimpleTypeDescr>() const
+{
+ return IsSimpleTypeDescrClass(getClass());
+}
+
+template <>
+inline bool
+JSObject::is<js::ComplexTypeDescr>() const
+{
+ return IsComplexTypeDescrClass(getClass());
+}
+
+template <>
+inline bool
+JSObject::is<js::TypeDescr>() const
+{
+ return IsTypeDescrClass(getClass());
+}
+
+template <>
+inline bool
+JSObject::is<js::TypedObject>() const
+{
+ return IsTypedObjectClass(getClass());
+}
+
+template <>
+inline bool
+JSObject::is<js::OutlineTypedObject>() const
+{
+ return getClass() == &js::OutlineTransparentTypedObject::class_ ||
+ getClass() == &js::OutlineOpaqueTypedObject::class_;
+}
+
+template <>
+inline bool
+JSObject::is<js::InlineTypedObject>() const
+{
+ return getClass() == &js::InlineTransparentTypedObject::class_ ||
+ getClass() == &js::InlineOpaqueTypedObject::class_;
+}
+
+#endif /* builtin_TypedObject_h */
diff --git a/js/src/builtin/TypedObject.js b/js/src/builtin/TypedObject.js
new file mode 100644
index 000000000..c4ddee486
--- /dev/null
+++ b/js/src/builtin/TypedObject.js
@@ -0,0 +1,1417 @@
+#include "TypedObjectConstants.h"
+
+///////////////////////////////////////////////////////////////////////////
+// Getters and setters for various slots.
+
+// Type object slots
+
+#define DESCR_KIND(obj) \
+ UnsafeGetInt32FromReservedSlot(obj, JS_DESCR_SLOT_KIND)
+#define DESCR_STRING_REPR(obj) \
+ UnsafeGetStringFromReservedSlot(obj, JS_DESCR_SLOT_STRING_REPR)
+#define DESCR_ALIGNMENT(obj) \
+ UnsafeGetInt32FromReservedSlot(obj, JS_DESCR_SLOT_ALIGNMENT)
+#define DESCR_SIZE(obj) \
+ UnsafeGetInt32FromReservedSlot(obj, JS_DESCR_SLOT_SIZE)
+#define DESCR_OPAQUE(obj) \
+ UnsafeGetBooleanFromReservedSlot(obj, JS_DESCR_SLOT_OPAQUE)
+#define DESCR_TYPE(obj) \
+ UnsafeGetInt32FromReservedSlot(obj, JS_DESCR_SLOT_TYPE)
+#define DESCR_ARRAY_ELEMENT_TYPE(obj) \
+ UnsafeGetObjectFromReservedSlot(obj, JS_DESCR_SLOT_ARRAY_ELEM_TYPE)
+#define DESCR_ARRAY_LENGTH(obj) \
+ TO_INT32(UnsafeGetInt32FromReservedSlot(obj, JS_DESCR_SLOT_ARRAY_LENGTH))
+#define DESCR_STRUCT_FIELD_NAMES(obj) \
+ UnsafeGetObjectFromReservedSlot(obj, JS_DESCR_SLOT_STRUCT_FIELD_NAMES)
+#define DESCR_STRUCT_FIELD_TYPES(obj) \
+ UnsafeGetObjectFromReservedSlot(obj, JS_DESCR_SLOT_STRUCT_FIELD_TYPES)
+#define DESCR_STRUCT_FIELD_OFFSETS(obj) \
+ UnsafeGetObjectFromReservedSlot(obj, JS_DESCR_SLOT_STRUCT_FIELD_OFFSETS)
+
+// Other
+
+#define HAS_PROPERTY(obj, prop) \
+ callFunction(std_Object_hasOwnProperty, obj, prop)
+
+///////////////////////////////////////////////////////////////////////////
+// Getting values
+//
+// The methods in this section read from the memory pointed at
+// by `this` and produce JS values. This process is called *reification*
+// in the spec.
+
+// Reifies the value referenced by the pointer, meaning that it
+// returns a new object pointing at the value. If the value is
+// a scalar, it will return a JS number, but otherwise the reified
+// result will be a typedObj of the same class as the ptr's typedObj.
+function TypedObjectGet(descr, typedObj, offset) {
+ assert(IsObject(descr) && ObjectIsTypeDescr(descr),
+ "get() called with bad type descr");
+
+ if (!TypedObjectIsAttached(typedObj))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED);
+
+ switch (DESCR_KIND(descr)) {
+ case JS_TYPEREPR_SCALAR_KIND:
+ return TypedObjectGetScalar(descr, typedObj, offset);
+
+ case JS_TYPEREPR_REFERENCE_KIND:
+ return TypedObjectGetReference(descr, typedObj, offset);
+
+ case JS_TYPEREPR_SIMD_KIND:
+ return TypedObjectGetSimd(descr, typedObj, offset);
+
+ case JS_TYPEREPR_ARRAY_KIND:
+ case JS_TYPEREPR_STRUCT_KIND:
+ return TypedObjectGetDerived(descr, typedObj, offset);
+ }
+
+ assert(false, "Unhandled kind: " + DESCR_KIND(descr));
+ return undefined;
+}
+
+function TypedObjectGetDerived(descr, typedObj, offset) {
+ assert(!TypeDescrIsSimpleType(descr),
+ "getDerived() used with simple type");
+ return NewDerivedTypedObject(descr, typedObj, offset);
+}
+
+function TypedObjectGetDerivedIf(descr, typedObj, offset, cond) {
+ return (cond ? TypedObjectGetDerived(descr, typedObj, offset) : undefined);
+}
+
+function TypedObjectGetOpaque(descr, typedObj, offset) {
+ assert(!TypeDescrIsSimpleType(descr),
+ "getDerived() used with simple type");
+ var opaqueTypedObj = NewOpaqueTypedObject(descr);
+ AttachTypedObject(opaqueTypedObj, typedObj, offset);
+ return opaqueTypedObj;
+}
+
+function TypedObjectGetOpaqueIf(descr, typedObj, offset, cond) {
+ return (cond ? TypedObjectGetOpaque(descr, typedObj, offset) : undefined);
+}
+
+function TypedObjectGetScalar(descr, typedObj, offset) {
+ var type = DESCR_TYPE(descr);
+ switch (type) {
+ case JS_SCALARTYPEREPR_INT8:
+ return Load_int8(typedObj, offset);
+
+ case JS_SCALARTYPEREPR_UINT8:
+ case JS_SCALARTYPEREPR_UINT8_CLAMPED:
+ return Load_uint8(typedObj, offset);
+
+ case JS_SCALARTYPEREPR_INT16:
+ return Load_int16(typedObj, offset);
+
+ case JS_SCALARTYPEREPR_UINT16:
+ return Load_uint16(typedObj, offset);
+
+ case JS_SCALARTYPEREPR_INT32:
+ return Load_int32(typedObj, offset);
+
+ case JS_SCALARTYPEREPR_UINT32:
+ return Load_uint32(typedObj, offset);
+
+ case JS_SCALARTYPEREPR_FLOAT32:
+ return Load_float32(typedObj, offset);
+
+ case JS_SCALARTYPEREPR_FLOAT64:
+ return Load_float64(typedObj, offset);
+ }
+
+ assert(false, "Unhandled scalar type: " + type);
+ return undefined;
+}
+
+function TypedObjectGetReference(descr, typedObj, offset) {
+ var type = DESCR_TYPE(descr);
+ switch (type) {
+ case JS_REFERENCETYPEREPR_ANY:
+ return Load_Any(typedObj, offset);
+
+ case JS_REFERENCETYPEREPR_OBJECT:
+ return Load_Object(typedObj, offset);
+
+ case JS_REFERENCETYPEREPR_STRING:
+ return Load_string(typedObj, offset);
+ }
+
+ assert(false, "Unhandled scalar type: " + type);
+ return undefined;
+}
+
+function TypedObjectGetSimd(descr, typedObj, offset) {
+ var type = DESCR_TYPE(descr);
+ var simdTypeDescr = GetSimdTypeDescr(type);
+ switch (type) {
+ case JS_SIMDTYPEREPR_FLOAT32X4:
+ var x = Load_float32(typedObj, offset + 0);
+ var y = Load_float32(typedObj, offset + 4);
+ var z = Load_float32(typedObj, offset + 8);
+ var w = Load_float32(typedObj, offset + 12);
+ return simdTypeDescr(x, y, z, w);
+
+ case JS_SIMDTYPEREPR_FLOAT64X2:
+ var x = Load_float64(typedObj, offset + 0);
+ var y = Load_float64(typedObj, offset + 8);
+ return simdTypeDescr(x, y);
+
+ case JS_SIMDTYPEREPR_INT8X16:
+ var s0 = Load_int8(typedObj, offset + 0);
+ var s1 = Load_int8(typedObj, offset + 1);
+ var s2 = Load_int8(typedObj, offset + 2);
+ var s3 = Load_int8(typedObj, offset + 3);
+ var s4 = Load_int8(typedObj, offset + 4);
+ var s5 = Load_int8(typedObj, offset + 5);
+ var s6 = Load_int8(typedObj, offset + 6);
+ var s7 = Load_int8(typedObj, offset + 7);
+ var s8 = Load_int8(typedObj, offset + 8);
+ var s9 = Load_int8(typedObj, offset + 9);
+ var s10 = Load_int8(typedObj, offset + 10);
+ var s11 = Load_int8(typedObj, offset + 11);
+ var s12 = Load_int8(typedObj, offset + 12);
+ var s13 = Load_int8(typedObj, offset + 13);
+ var s14 = Load_int8(typedObj, offset + 14);
+ var s15 = Load_int8(typedObj, offset + 15);
+ return simdTypeDescr(s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15);
+
+ case JS_SIMDTYPEREPR_INT16X8:
+ var s0 = Load_int16(typedObj, offset + 0);
+ var s1 = Load_int16(typedObj, offset + 2);
+ var s2 = Load_int16(typedObj, offset + 4);
+ var s3 = Load_int16(typedObj, offset + 6);
+ var s4 = Load_int16(typedObj, offset + 8);
+ var s5 = Load_int16(typedObj, offset + 10);
+ var s6 = Load_int16(typedObj, offset + 12);
+ var s7 = Load_int16(typedObj, offset + 14);
+ return simdTypeDescr(s0, s1, s2, s3, s4, s5, s6, s7);
+
+ case JS_SIMDTYPEREPR_INT32X4:
+ var x = Load_int32(typedObj, offset + 0);
+ var y = Load_int32(typedObj, offset + 4);
+ var z = Load_int32(typedObj, offset + 8);
+ var w = Load_int32(typedObj, offset + 12);
+ return simdTypeDescr(x, y, z, w);
+
+ case JS_SIMDTYPEREPR_UINT8X16:
+ var s0 = Load_uint8(typedObj, offset + 0);
+ var s1 = Load_uint8(typedObj, offset + 1);
+ var s2 = Load_uint8(typedObj, offset + 2);
+ var s3 = Load_uint8(typedObj, offset + 3);
+ var s4 = Load_uint8(typedObj, offset + 4);
+ var s5 = Load_uint8(typedObj, offset + 5);
+ var s6 = Load_uint8(typedObj, offset + 6);
+ var s7 = Load_uint8(typedObj, offset + 7);
+ var s8 = Load_uint8(typedObj, offset + 8);
+ var s9 = Load_uint8(typedObj, offset + 9);
+ var s10 = Load_uint8(typedObj, offset + 10);
+ var s11 = Load_uint8(typedObj, offset + 11);
+ var s12 = Load_uint8(typedObj, offset + 12);
+ var s13 = Load_uint8(typedObj, offset + 13);
+ var s14 = Load_uint8(typedObj, offset + 14);
+ var s15 = Load_uint8(typedObj, offset + 15);
+ return simdTypeDescr(s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15);
+
+ case JS_SIMDTYPEREPR_UINT16X8:
+ var s0 = Load_uint16(typedObj, offset + 0);
+ var s1 = Load_uint16(typedObj, offset + 2);
+ var s2 = Load_uint16(typedObj, offset + 4);
+ var s3 = Load_uint16(typedObj, offset + 6);
+ var s4 = Load_uint16(typedObj, offset + 8);
+ var s5 = Load_uint16(typedObj, offset + 10);
+ var s6 = Load_uint16(typedObj, offset + 12);
+ var s7 = Load_uint16(typedObj, offset + 14);
+ return simdTypeDescr(s0, s1, s2, s3, s4, s5, s6, s7);
+
+ case JS_SIMDTYPEREPR_UINT32X4:
+ var x = Load_uint32(typedObj, offset + 0);
+ var y = Load_uint32(typedObj, offset + 4);
+ var z = Load_uint32(typedObj, offset + 8);
+ var w = Load_uint32(typedObj, offset + 12);
+ return simdTypeDescr(x, y, z, w);
+
+ case JS_SIMDTYPEREPR_BOOL8X16:
+ var s0 = Load_int8(typedObj, offset + 0);
+ var s1 = Load_int8(typedObj, offset + 1);
+ var s2 = Load_int8(typedObj, offset + 2);
+ var s3 = Load_int8(typedObj, offset + 3);
+ var s4 = Load_int8(typedObj, offset + 4);
+ var s5 = Load_int8(typedObj, offset + 5);
+ var s6 = Load_int8(typedObj, offset + 6);
+ var s7 = Load_int8(typedObj, offset + 7);
+ var s8 = Load_int8(typedObj, offset + 8);
+ var s9 = Load_int8(typedObj, offset + 9);
+ var s10 = Load_int8(typedObj, offset + 10);
+ var s11 = Load_int8(typedObj, offset + 11);
+ var s12 = Load_int8(typedObj, offset + 12);
+ var s13 = Load_int8(typedObj, offset + 13);
+ var s14 = Load_int8(typedObj, offset + 14);
+ var s15 = Load_int8(typedObj, offset + 15);
+ return simdTypeDescr(s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15);
+
+ case JS_SIMDTYPEREPR_BOOL16X8:
+ var s0 = Load_int16(typedObj, offset + 0);
+ var s1 = Load_int16(typedObj, offset + 2);
+ var s2 = Load_int16(typedObj, offset + 4);
+ var s3 = Load_int16(typedObj, offset + 6);
+ var s4 = Load_int16(typedObj, offset + 8);
+ var s5 = Load_int16(typedObj, offset + 10);
+ var s6 = Load_int16(typedObj, offset + 12);
+ var s7 = Load_int16(typedObj, offset + 14);
+ return simdTypeDescr(s0, s1, s2, s3, s4, s5, s6, s7);
+
+ case JS_SIMDTYPEREPR_BOOL32X4:
+ var x = Load_int32(typedObj, offset + 0);
+ var y = Load_int32(typedObj, offset + 4);
+ var z = Load_int32(typedObj, offset + 8);
+ var w = Load_int32(typedObj, offset + 12);
+ return simdTypeDescr(x, y, z, w);
+
+ case JS_SIMDTYPEREPR_BOOL64X2:
+ var x = Load_int32(typedObj, offset + 0);
+ var y = Load_int32(typedObj, offset + 8);
+ return simdTypeDescr(x, y);
+
+ }
+
+ assert(false, "Unhandled SIMD type: " + type);
+ return undefined;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Setting values
+//
+// The methods in this section modify the data pointed at by `this`.
+
+// Writes `fromValue` into the `typedObj` at offset `offset`, adapting
+// it to `descr` as needed. This is the most general entry point
+// and works for any type.
+function TypedObjectSet(descr, typedObj, offset, name, fromValue) {
+ if (!TypedObjectIsAttached(typedObj))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED);
+
+ switch (DESCR_KIND(descr)) {
+ case JS_TYPEREPR_SCALAR_KIND:
+ TypedObjectSetScalar(descr, typedObj, offset, fromValue);
+ return;
+
+ case JS_TYPEREPR_REFERENCE_KIND:
+ TypedObjectSetReference(descr, typedObj, offset, name, fromValue);
+ return;
+
+ case JS_TYPEREPR_SIMD_KIND:
+ TypedObjectSetSimd(descr, typedObj, offset, fromValue);
+ return;
+
+ case JS_TYPEREPR_ARRAY_KIND:
+ var length = DESCR_ARRAY_LENGTH(descr);
+ if (TypedObjectSetArray(descr, length, typedObj, offset, fromValue))
+ return;
+ break;
+
+ case JS_TYPEREPR_STRUCT_KIND:
+ if (!IsObject(fromValue))
+ break;
+
+ // Adapt each field.
+ var fieldNames = DESCR_STRUCT_FIELD_NAMES(descr);
+ var fieldDescrs = DESCR_STRUCT_FIELD_TYPES(descr);
+ var fieldOffsets = DESCR_STRUCT_FIELD_OFFSETS(descr);
+ for (var i = 0; i < fieldNames.length; i++) {
+ var fieldName = fieldNames[i];
+ var fieldDescr = fieldDescrs[i];
+ var fieldOffset = fieldOffsets[i];
+ var fieldValue = fromValue[fieldName];
+ TypedObjectSet(fieldDescr, typedObj, offset + fieldOffset, fieldName, fieldValue);
+ }
+ return;
+ }
+
+ ThrowTypeError(JSMSG_CANT_CONVERT_TO,
+ typeof(fromValue),
+ DESCR_STRING_REPR(descr));
+}
+
+function TypedObjectSetArray(descr, length, typedObj, offset, fromValue) {
+ if (!IsObject(fromValue))
+ return false;
+
+ // Check that "array-like" fromValue has an appropriate length.
+ if (fromValue.length !== length)
+ return false;
+
+ // Adapt each element.
+ if (length > 0) {
+ var elemDescr = DESCR_ARRAY_ELEMENT_TYPE(descr);
+ var elemSize = DESCR_SIZE(elemDescr);
+ var elemOffset = offset;
+ for (var i = 0; i < length; i++) {
+ TypedObjectSet(elemDescr, typedObj, elemOffset, null, fromValue[i]);
+ elemOffset += elemSize;
+ }
+ }
+ return true;
+}
+
+// Sets `fromValue` to `this` assuming that `this` is a scalar type.
+function TypedObjectSetScalar(descr, typedObj, offset, fromValue) {
+ assert(DESCR_KIND(descr) === JS_TYPEREPR_SCALAR_KIND,
+ "Expected scalar type descriptor");
+ var type = DESCR_TYPE(descr);
+ switch (type) {
+ case JS_SCALARTYPEREPR_INT8:
+ return Store_int8(typedObj, offset,
+ TO_INT32(fromValue) & 0xFF);
+
+ case JS_SCALARTYPEREPR_UINT8:
+ return Store_uint8(typedObj, offset,
+ TO_UINT32(fromValue) & 0xFF);
+
+ case JS_SCALARTYPEREPR_UINT8_CLAMPED:
+ var v = ClampToUint8(+fromValue);
+ return Store_int8(typedObj, offset, v);
+
+ case JS_SCALARTYPEREPR_INT16:
+ return Store_int16(typedObj, offset,
+ TO_INT32(fromValue) & 0xFFFF);
+
+ case JS_SCALARTYPEREPR_UINT16:
+ return Store_uint16(typedObj, offset,
+ TO_UINT32(fromValue) & 0xFFFF);
+
+ case JS_SCALARTYPEREPR_INT32:
+ return Store_int32(typedObj, offset,
+ TO_INT32(fromValue));
+
+ case JS_SCALARTYPEREPR_UINT32:
+ return Store_uint32(typedObj, offset,
+ TO_UINT32(fromValue));
+
+ case JS_SCALARTYPEREPR_FLOAT32:
+ return Store_float32(typedObj, offset, +fromValue);
+
+ case JS_SCALARTYPEREPR_FLOAT64:
+ return Store_float64(typedObj, offset, +fromValue);
+ }
+
+ assert(false, "Unhandled scalar type: " + type);
+ return undefined;
+}
+
+function TypedObjectSetReference(descr, typedObj, offset, name, fromValue) {
+ var type = DESCR_TYPE(descr);
+ switch (type) {
+ case JS_REFERENCETYPEREPR_ANY:
+ return Store_Any(typedObj, offset, name, fromValue);
+
+ case JS_REFERENCETYPEREPR_OBJECT:
+ var value = (fromValue === null ? fromValue : ToObject(fromValue));
+ return Store_Object(typedObj, offset, name, value);
+
+ case JS_REFERENCETYPEREPR_STRING:
+ return Store_string(typedObj, offset, name, ToString(fromValue));
+ }
+
+ assert(false, "Unhandled scalar type: " + type);
+ return undefined;
+}
+
+// Sets `fromValue` to `this` assuming that `this` is a scalar type.
+function TypedObjectSetSimd(descr, typedObj, offset, fromValue) {
+ if (!IsObject(fromValue) || !ObjectIsTypedObject(fromValue))
+ ThrowTypeError(JSMSG_CANT_CONVERT_TO,
+ typeof(fromValue),
+ DESCR_STRING_REPR(descr));
+
+ if (!DescrsEquiv(descr, TypedObjectTypeDescr(fromValue)))
+ ThrowTypeError(JSMSG_CANT_CONVERT_TO,
+ typeof(fromValue),
+ DESCR_STRING_REPR(descr));
+
+ var type = DESCR_TYPE(descr);
+ switch (type) {
+ case JS_SIMDTYPEREPR_FLOAT32X4:
+ Store_float32(typedObj, offset + 0, Load_float32(fromValue, 0));
+ Store_float32(typedObj, offset + 4, Load_float32(fromValue, 4));
+ Store_float32(typedObj, offset + 8, Load_float32(fromValue, 8));
+ Store_float32(typedObj, offset + 12, Load_float32(fromValue, 12));
+ break;
+ case JS_SIMDTYPEREPR_FLOAT64X2:
+ Store_float64(typedObj, offset + 0, Load_float64(fromValue, 0));
+ Store_float64(typedObj, offset + 8, Load_float64(fromValue, 8));
+ break;
+ case JS_SIMDTYPEREPR_INT8X16:
+ case JS_SIMDTYPEREPR_BOOL8X16:
+ Store_int8(typedObj, offset + 0, Load_int8(fromValue, 0));
+ Store_int8(typedObj, offset + 1, Load_int8(fromValue, 1));
+ Store_int8(typedObj, offset + 2, Load_int8(fromValue, 2));
+ Store_int8(typedObj, offset + 3, Load_int8(fromValue, 3));
+ Store_int8(typedObj, offset + 4, Load_int8(fromValue, 4));
+ Store_int8(typedObj, offset + 5, Load_int8(fromValue, 5));
+ Store_int8(typedObj, offset + 6, Load_int8(fromValue, 6));
+ Store_int8(typedObj, offset + 7, Load_int8(fromValue, 7));
+ Store_int8(typedObj, offset + 8, Load_int8(fromValue, 8));
+ Store_int8(typedObj, offset + 9, Load_int8(fromValue, 9));
+ Store_int8(typedObj, offset + 10, Load_int8(fromValue, 10));
+ Store_int8(typedObj, offset + 11, Load_int8(fromValue, 11));
+ Store_int8(typedObj, offset + 12, Load_int8(fromValue, 12));
+ Store_int8(typedObj, offset + 13, Load_int8(fromValue, 13));
+ Store_int8(typedObj, offset + 14, Load_int8(fromValue, 14));
+ Store_int8(typedObj, offset + 15, Load_int8(fromValue, 15));
+ break;
+ case JS_SIMDTYPEREPR_INT16X8:
+ case JS_SIMDTYPEREPR_BOOL16X8:
+ Store_int16(typedObj, offset + 0, Load_int16(fromValue, 0));
+ Store_int16(typedObj, offset + 2, Load_int16(fromValue, 2));
+ Store_int16(typedObj, offset + 4, Load_int16(fromValue, 4));
+ Store_int16(typedObj, offset + 6, Load_int16(fromValue, 6));
+ Store_int16(typedObj, offset + 8, Load_int16(fromValue, 8));
+ Store_int16(typedObj, offset + 10, Load_int16(fromValue, 10));
+ Store_int16(typedObj, offset + 12, Load_int16(fromValue, 12));
+ Store_int16(typedObj, offset + 14, Load_int16(fromValue, 14));
+ break;
+ case JS_SIMDTYPEREPR_INT32X4:
+ case JS_SIMDTYPEREPR_BOOL32X4:
+ case JS_SIMDTYPEREPR_BOOL64X2:
+ Store_int32(typedObj, offset + 0, Load_int32(fromValue, 0));
+ Store_int32(typedObj, offset + 4, Load_int32(fromValue, 4));
+ Store_int32(typedObj, offset + 8, Load_int32(fromValue, 8));
+ Store_int32(typedObj, offset + 12, Load_int32(fromValue, 12));
+ break;
+ case JS_SIMDTYPEREPR_UINT8X16:
+ Store_uint8(typedObj, offset + 0, Load_uint8(fromValue, 0));
+ Store_uint8(typedObj, offset + 1, Load_uint8(fromValue, 1));
+ Store_uint8(typedObj, offset + 2, Load_uint8(fromValue, 2));
+ Store_uint8(typedObj, offset + 3, Load_uint8(fromValue, 3));
+ Store_uint8(typedObj, offset + 4, Load_uint8(fromValue, 4));
+ Store_uint8(typedObj, offset + 5, Load_uint8(fromValue, 5));
+ Store_uint8(typedObj, offset + 6, Load_uint8(fromValue, 6));
+ Store_uint8(typedObj, offset + 7, Load_uint8(fromValue, 7));
+ Store_uint8(typedObj, offset + 8, Load_uint8(fromValue, 8));
+ Store_uint8(typedObj, offset + 9, Load_uint8(fromValue, 9));
+ Store_uint8(typedObj, offset + 10, Load_uint8(fromValue, 10));
+ Store_uint8(typedObj, offset + 11, Load_uint8(fromValue, 11));
+ Store_uint8(typedObj, offset + 12, Load_uint8(fromValue, 12));
+ Store_uint8(typedObj, offset + 13, Load_uint8(fromValue, 13));
+ Store_uint8(typedObj, offset + 14, Load_uint8(fromValue, 14));
+ Store_uint8(typedObj, offset + 15, Load_uint8(fromValue, 15));
+ break;
+ case JS_SIMDTYPEREPR_UINT16X8:
+ Store_uint16(typedObj, offset + 0, Load_uint16(fromValue, 0));
+ Store_uint16(typedObj, offset + 2, Load_uint16(fromValue, 2));
+ Store_uint16(typedObj, offset + 4, Load_uint16(fromValue, 4));
+ Store_uint16(typedObj, offset + 6, Load_uint16(fromValue, 6));
+ Store_uint16(typedObj, offset + 8, Load_uint16(fromValue, 8));
+ Store_uint16(typedObj, offset + 10, Load_uint16(fromValue, 10));
+ Store_uint16(typedObj, offset + 12, Load_uint16(fromValue, 12));
+ Store_uint16(typedObj, offset + 14, Load_uint16(fromValue, 14));
+ break;
+ case JS_SIMDTYPEREPR_UINT32X4:
+ Store_uint32(typedObj, offset + 0, Load_uint32(fromValue, 0));
+ Store_uint32(typedObj, offset + 4, Load_uint32(fromValue, 4));
+ Store_uint32(typedObj, offset + 8, Load_uint32(fromValue, 8));
+ Store_uint32(typedObj, offset + 12, Load_uint32(fromValue, 12));
+ break;
+ default:
+ assert(false, "Unhandled Simd type: " + type);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// C++ Wrappers
+//
+// These helpers are invoked by C++ code or used as method bodies.
+
+// Wrapper for use from C++ code.
+function ConvertAndCopyTo(destDescr,
+ destTypedObj,
+ destOffset,
+ fieldName,
+ fromValue)
+{
+ assert(IsObject(destDescr) && ObjectIsTypeDescr(destDescr),
+ "ConvertAndCopyTo: not type obj");
+ assert(IsObject(destTypedObj) && ObjectIsTypedObject(destTypedObj),
+ "ConvertAndCopyTo: not type typedObj");
+
+ if (!TypedObjectIsAttached(destTypedObj))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED);
+
+ TypedObjectSet(destDescr, destTypedObj, destOffset, fieldName, fromValue);
+}
+
+// Wrapper for use from C++ code.
+function Reify(sourceDescr,
+ sourceTypedObj,
+ sourceOffset) {
+ assert(IsObject(sourceDescr) && ObjectIsTypeDescr(sourceDescr),
+ "Reify: not type obj");
+ assert(IsObject(sourceTypedObj) && ObjectIsTypedObject(sourceTypedObj),
+ "Reify: not type typedObj");
+
+ if (!TypedObjectIsAttached(sourceTypedObj))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED);
+
+ return TypedObjectGet(sourceDescr, sourceTypedObj, sourceOffset);
+}
+
+// Warning: user exposed!
+function TypeDescrEquivalent(otherDescr) {
+ if (!IsObject(this) || !ObjectIsTypeDescr(this))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+ if (!IsObject(otherDescr) || !ObjectIsTypeDescr(otherDescr))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+ return DescrsEquiv(this, otherDescr);
+}
+
+// TypedObjectArray.redimension(newArrayType)
+//
+// Method that "repackages" the data from this array into a new typed
+// object whose type is `newArrayType`. Once you strip away all the
+// outer array dimensions, the type of `this` array and `newArrayType`
+// must share the same innermost element type. Moreover, those
+// stripped away dimensions must amount to the same total number of
+// elements.
+//
+// For example, given two equivalent types `T` and `U`, it is legal to
+// interconvert between arrays types like:
+// T[32]
+// U[2][16]
+// U[2][2][8]
+// Because they all share the same total number (32) of equivalent elements.
+// But it would be illegal to convert `T[32]` to `U[31]` or `U[2][17]`, since
+// the number of elements differs. And it's just plain incompatible to convert
+// if the base element types are not equivalent.
+//
+// Warning: user exposed!
+function TypedObjectArrayRedimension(newArrayType) {
+ if (!IsObject(this) || !ObjectIsTypedObject(this))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+ if (!IsObject(newArrayType) || !ObjectIsTypeDescr(newArrayType))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+ // Peel away the outermost array layers from the type of `this` to find
+ // the core element type. In the process, count the number of elements.
+ var oldArrayType = TypedObjectTypeDescr(this);
+ var oldElementType = oldArrayType;
+ var oldElementCount = 1;
+
+ if (DESCR_KIND(oldArrayType) != JS_TYPEREPR_ARRAY_KIND)
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+ while (DESCR_KIND(oldElementType) === JS_TYPEREPR_ARRAY_KIND) {
+ oldElementCount *= oldElementType.length;
+ oldElementType = oldElementType.elementType;
+ }
+
+ // Peel away the outermost array layers from `newArrayType`. In the
+ // process, count the number of elements.
+ var newElementType = newArrayType;
+ var newElementCount = 1;
+ while (DESCR_KIND(newElementType) == JS_TYPEREPR_ARRAY_KIND) {
+ newElementCount *= newElementType.length;
+ newElementType = newElementType.elementType;
+ }
+
+ // Check that the total number of elements does not change.
+ if (oldElementCount !== newElementCount) {
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+ }
+
+ // Check that the element types are equivalent.
+ if (!DescrsEquiv(oldElementType, newElementType)) {
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+ }
+
+ // Together, this should imply that the sizes are unchanged.
+ assert(DESCR_SIZE(oldArrayType) == DESCR_SIZE(newArrayType),
+ "Byte sizes should be equal");
+
+ // Rewrap the data from `this` in a new type.
+ return NewDerivedTypedObject(newArrayType, this, 0);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// SIMD
+
+function SimdProtoString(type) {
+ switch (type) {
+ case JS_SIMDTYPEREPR_INT8X16:
+ return "Int8x16";
+ case JS_SIMDTYPEREPR_INT16X8:
+ return "Int16x8";
+ case JS_SIMDTYPEREPR_INT32X4:
+ return "Int32x4";
+ case JS_SIMDTYPEREPR_UINT8X16:
+ return "Uint8x16";
+ case JS_SIMDTYPEREPR_UINT16X8:
+ return "Uint16x8";
+ case JS_SIMDTYPEREPR_UINT32X4:
+ return "Uint32x4";
+ case JS_SIMDTYPEREPR_FLOAT32X4:
+ return "Float32x4";
+ case JS_SIMDTYPEREPR_FLOAT64X2:
+ return "Float64x2";
+ case JS_SIMDTYPEREPR_BOOL8X16:
+ return "Bool8x16";
+ case JS_SIMDTYPEREPR_BOOL16X8:
+ return "Bool16x8";
+ case JS_SIMDTYPEREPR_BOOL32X4:
+ return "Bool32x4";
+ case JS_SIMDTYPEREPR_BOOL64X2:
+ return "Bool64x2";
+ }
+
+ assert(false, "Unhandled type constant");
+ return undefined;
+}
+
+function SimdTypeToLength(type) {
+ switch (type) {
+ case JS_SIMDTYPEREPR_INT8X16:
+ case JS_SIMDTYPEREPR_BOOL8X16:
+ return 16;
+ case JS_SIMDTYPEREPR_INT16X8:
+ case JS_SIMDTYPEREPR_BOOL16X8:
+ return 8;
+ case JS_SIMDTYPEREPR_INT32X4:
+ case JS_SIMDTYPEREPR_FLOAT32X4:
+ case JS_SIMDTYPEREPR_BOOL32X4:
+ return 4;
+ case JS_SIMDTYPEREPR_FLOAT64X2:
+ case JS_SIMDTYPEREPR_BOOL64X2:
+ return 2;
+ }
+
+ assert(false, "Unhandled type constant");
+ return undefined;
+}
+
+// This implements SIMD.*.prototype.valueOf().
+// Once we have proper value semantics for SIMD types, this function should just
+// perform a type check and return this.
+// For now, throw a TypeError unconditionally since valueOf() was probably
+// called from ToNumber() which is supposed to throw when attempting to convert
+// a SIMD value to a number.
+function SimdValueOf() {
+ if (!IsObject(this) || !ObjectIsTypedObject(this))
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "SIMD", "valueOf", typeof this);
+
+ var descr = TypedObjectTypeDescr(this);
+
+ if (DESCR_KIND(descr) != JS_TYPEREPR_SIMD_KIND)
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "SIMD", "valueOf", typeof this);
+
+ ThrowTypeError(JSMSG_SIMD_TO_NUMBER);
+}
+
+function SimdToSource() {
+ if (!IsObject(this) || !ObjectIsTypedObject(this))
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "SIMD.*", "toSource", typeof this);
+
+ var descr = TypedObjectTypeDescr(this);
+
+ if (DESCR_KIND(descr) != JS_TYPEREPR_SIMD_KIND)
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "SIMD.*", "toSource", typeof this);
+
+ return SimdFormatString(descr, this);
+}
+
+function SimdToString() {
+ if (!IsObject(this) || !ObjectIsTypedObject(this))
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "SIMD.*", "toString", typeof this);
+
+ var descr = TypedObjectTypeDescr(this);
+
+ if (DESCR_KIND(descr) != JS_TYPEREPR_SIMD_KIND)
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "SIMD.*", "toString", typeof this);
+
+ return SimdFormatString(descr, this);
+}
+
+function SimdFormatString(descr, typedObj) {
+ var typerepr = DESCR_TYPE(descr);
+ var protoString = SimdProtoString(typerepr);
+ switch (typerepr) {
+ case JS_SIMDTYPEREPR_INT8X16: {
+ var s1 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 0);
+ var s2 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 1);
+ var s3 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 2);
+ var s4 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 3);
+ var s5 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 4);
+ var s6 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 5);
+ var s7 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 6);
+ var s8 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 7);
+ var s9 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 8);
+ var s10 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 9);
+ var s11 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 10);
+ var s12 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 11);
+ var s13 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 12);
+ var s14 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 13);
+ var s15 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 14);
+ var s16 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 15);
+ return `SIMD.${protoString}(${s1}, ${s2}, ${s3}, ${s4}, ${s5}, ${s6}, ${s7}, ${s8}, ${s9}, ${s10}, ${s11}, ${s12}, ${s13}, ${s14}, ${s15}, ${s16})`;
+ }
+ case JS_SIMDTYPEREPR_INT16X8: {
+ var s1 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 0);
+ var s2 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 1);
+ var s3 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 2);
+ var s4 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 3);
+ var s5 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 4);
+ var s6 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 5);
+ var s7 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 6);
+ var s8 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 7);
+ return `SIMD.${protoString}(${s1}, ${s2}, ${s3}, ${s4}, ${s5}, ${s6}, ${s7}, ${s8})`;
+ }
+ case JS_SIMDTYPEREPR_INT32X4: {
+ var x = callFunction(std_SIMD_Int32x4_extractLane, null, typedObj, 0);
+ var y = callFunction(std_SIMD_Int32x4_extractLane, null, typedObj, 1);
+ var z = callFunction(std_SIMD_Int32x4_extractLane, null, typedObj, 2);
+ var w = callFunction(std_SIMD_Int32x4_extractLane, null, typedObj, 3);
+ return `SIMD.${protoString}(${x}, ${y}, ${z}, ${w})`;
+ }
+ case JS_SIMDTYPEREPR_UINT8X16: {
+ var s1 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 0);
+ var s2 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 1);
+ var s3 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 2);
+ var s4 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 3);
+ var s5 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 4);
+ var s6 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 5);
+ var s7 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 6);
+ var s8 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 7);
+ var s9 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 8);
+ var s10 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 9);
+ var s11 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 10);
+ var s12 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 11);
+ var s13 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 12);
+ var s14 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 13);
+ var s15 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 14);
+ var s16 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 15);
+ return `SIMD.${protoString}(${s1}, ${s2}, ${s3}, ${s4}, ${s5}, ${s6}, ${s7}, ${s8}, ${s9}, ${s10}, ${s11}, ${s12}, ${s13}, ${s14}, ${s15}, ${s16})`;
+ }
+ case JS_SIMDTYPEREPR_UINT16X8: {
+ var s1 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 0);
+ var s2 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 1);
+ var s3 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 2);
+ var s4 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 3);
+ var s5 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 4);
+ var s6 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 5);
+ var s7 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 6);
+ var s8 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 7);
+ return `SIMD.${protoString}(${s1}, ${s2}, ${s3}, ${s4}, ${s5}, ${s6}, ${s7}, ${s8})`;
+ }
+ case JS_SIMDTYPEREPR_UINT32X4: {
+ var x = callFunction(std_SIMD_Uint32x4_extractLane, null, typedObj, 0);
+ var y = callFunction(std_SIMD_Uint32x4_extractLane, null, typedObj, 1);
+ var z = callFunction(std_SIMD_Uint32x4_extractLane, null, typedObj, 2);
+ var w = callFunction(std_SIMD_Uint32x4_extractLane, null, typedObj, 3);
+ return `SIMD.${protoString}(${x}, ${y}, ${z}, ${w})`;
+ }
+ case JS_SIMDTYPEREPR_FLOAT32X4: {
+ var x = callFunction(std_SIMD_Float32x4_extractLane, null, typedObj, 0);
+ var y = callFunction(std_SIMD_Float32x4_extractLane, null, typedObj, 1);
+ var z = callFunction(std_SIMD_Float32x4_extractLane, null, typedObj, 2);
+ var w = callFunction(std_SIMD_Float32x4_extractLane, null, typedObj, 3);
+ return `SIMD.${protoString}(${x}, ${y}, ${z}, ${w})`;
+ }
+ case JS_SIMDTYPEREPR_FLOAT64X2: {
+ var x = callFunction(std_SIMD_Float64x2_extractLane, null, typedObj, 0);
+ var y = callFunction(std_SIMD_Float64x2_extractLane, null, typedObj, 1);
+ return `SIMD.${protoString}(${x}, ${y})`;
+ }
+ case JS_SIMDTYPEREPR_BOOL8X16: {
+ var s1 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 0);
+ var s2 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 1);
+ var s3 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 2);
+ var s4 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 3);
+ var s5 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 4);
+ var s6 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 5);
+ var s7 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 6);
+ var s8 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 7);
+ var s9 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 8);
+ var s10 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 9);
+ var s11 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 10);
+ var s12 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 11);
+ var s13 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 12);
+ var s14 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 13);
+ var s15 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 14);
+ var s16 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 15);
+ return `SIMD.${protoString}(${s1}, ${s2}, ${s3}, ${s4}, ${s5}, ${s6}, ${s7}, ${s8}, ${s9}, ${s10}, ${s11}, ${s12}, ${s13}, ${s14}, ${s15}, ${s16})`;
+ }
+ case JS_SIMDTYPEREPR_BOOL16X8: {
+ var s1 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 0);
+ var s2 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 1);
+ var s3 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 2);
+ var s4 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 3);
+ var s5 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 4);
+ var s6 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 5);
+ var s7 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 6);
+ var s8 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 7);
+ return `SIMD.${protoString}(${s1}, ${s2}, ${s3}, ${s4}, ${s5}, ${s6}, ${s7}, ${s8})`;
+ }
+ case JS_SIMDTYPEREPR_BOOL32X4: {
+ var x = callFunction(std_SIMD_Bool32x4_extractLane, null, typedObj, 0);
+ var y = callFunction(std_SIMD_Bool32x4_extractLane, null, typedObj, 1);
+ var z = callFunction(std_SIMD_Bool32x4_extractLane, null, typedObj, 2);
+ var w = callFunction(std_SIMD_Bool32x4_extractLane, null, typedObj, 3);
+ return `SIMD.${protoString}(${x}, ${y}, ${z}, ${w})`;
+ }
+ case JS_SIMDTYPEREPR_BOOL64X2: {
+ var x = callFunction(std_SIMD_Bool64x2_extractLane, null, typedObj, 0);
+ var y = callFunction(std_SIMD_Bool64x2_extractLane, null, typedObj, 1);
+ return `SIMD.${protoString}(${x}, ${y})`;
+ }
+ }
+ assert(false, "unexpected SIMD kind");
+ return '?';
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Miscellaneous
+
+function DescrsEquiv(descr1, descr2) {
+ assert(IsObject(descr1) && ObjectIsTypeDescr(descr1), "descr1 not descr");
+ assert(IsObject(descr2) && ObjectIsTypeDescr(descr2), "descr2 not descr");
+
+ // Potential optimization: these two strings are guaranteed to be
+ // atoms, and hence this string comparison can just be a pointer
+ // comparison. However, I don't think ion knows that. If this ever
+ // becomes a bottleneck, we can add a intrinsic at some point that
+ // is treated specially by Ion. (Bug 976688)
+
+ return DESCR_STRING_REPR(descr1) === DESCR_STRING_REPR(descr2);
+}
+
+// toSource() for type descriptors.
+//
+// Warning: user exposed!
+function DescrToSource() {
+ if (!IsObject(this) || !ObjectIsTypeDescr(this))
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "Type", "toSource", "value");
+
+ return DESCR_STRING_REPR(this);
+}
+
+// Warning: user exposed!
+function ArrayShorthand(...dims) {
+ if (!IsObject(this) || !ObjectIsTypeDescr(this))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+ var AT = GetTypedObjectModule().ArrayType;
+
+ if (dims.length == 0)
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+ var accum = this;
+ for (var i = dims.length - 1; i >= 0; i--)
+ accum = new AT(accum, dims[i]);
+ return accum;
+}
+
+// This is the `storage()` function defined in the spec. When
+// provided with a *transparent* typed object, it returns an object
+// containing buffer, byteOffset, byteLength. When given an opaque
+// typed object, it returns null. Otherwise it throws.
+//
+// Warning: user exposed!
+function StorageOfTypedObject(obj) {
+ if (IsObject(obj)) {
+ if (ObjectIsOpaqueTypedObject(obj))
+ return null;
+
+ if (ObjectIsTransparentTypedObject(obj)) {
+ if (!TypedObjectIsAttached(obj))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED);
+
+ var descr = TypedObjectTypeDescr(obj);
+ var byteLength = DESCR_SIZE(descr);
+
+ return { buffer: TypedObjectBuffer(obj),
+ byteLength: byteLength,
+ byteOffset: TypedObjectByteOffset(obj) };
+ }
+ }
+
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+}
+
+// This is the `objectType()` function defined in the spec.
+// It returns the type of its argument.
+//
+// Warning: user exposed!
+function TypeOfTypedObject(obj) {
+ if (IsObject(obj) && ObjectIsTypedObject(obj))
+ return TypedObjectTypeDescr(obj);
+
+ // Note: Do not create bindings for `Any`, `String`, etc in
+ // Utilities.js, but rather access them through
+ // `GetTypedObjectModule()`. The reason is that bindings
+ // you create in Utilities.js are part of the self-hosted global,
+ // vs the user-accessible global, and hence should not escape to
+ // user script.
+ var T = GetTypedObjectModule();
+ switch (typeof obj) {
+ case "object": return T.Object;
+ case "function": return T.Object;
+ case "string": return T.String;
+ case "number": return T.float64;
+ case "undefined": return T.Any;
+ default: return T.Any;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// TypedObject surface API methods (sequential implementations).
+
+// Warning: user exposed!
+function TypedObjectArrayTypeBuild(a,b,c) {
+ // Arguments : [depth], func
+
+ if (!IsObject(this) || !ObjectIsTypeDescr(this))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+ var kind = DESCR_KIND(this);
+ switch (kind) {
+ case JS_TYPEREPR_ARRAY_KIND:
+ if (typeof a === "function") // XXX here and elsewhere: these type dispatches are fragile at best.
+ return BuildTypedSeqImpl(this, this.length, 1, a);
+ else if (typeof a === "number" && typeof b === "function")
+ return BuildTypedSeqImpl(this, this.length, a, b);
+ else if (typeof a === "number")
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+ else
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+ default:
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+ }
+}
+
+// Warning: user exposed!
+function TypedObjectArrayTypeFrom(a, b, c) {
+ // Arguments: arrayLike, [depth], func
+
+ if (!IsObject(this) || !ObjectIsTypeDescr(this))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+ var untypedInput = !IsObject(a) || !ObjectIsTypedObject(a) ||
+ !TypeDescrIsArrayType(TypedObjectTypeDescr(a));
+
+ // for untyped input array, the expectation (in terms of error
+ // reporting for invalid parameters) is no-depth, despite
+ // supporting an explicit depth of 1; while for typed input array,
+ // the expectation is explicit depth.
+
+ if (untypedInput) {
+ if (b === 1 && IsCallable(c))
+ return MapUntypedSeqImpl(a, this, c);
+ if (IsCallable(b))
+ return MapUntypedSeqImpl(a, this, b);
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+ }
+
+ if (typeof b === "number" && IsCallable(c))
+ return MapTypedSeqImpl(a, b, this, c);
+ if (IsCallable(b))
+ return MapTypedSeqImpl(a, 1, this, b);
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+}
+
+// Warning: user exposed!
+function TypedObjectArrayMap(a, b) {
+ if (!IsObject(this) || !ObjectIsTypedObject(this))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+ var thisType = TypedObjectTypeDescr(this);
+ if (!TypeDescrIsArrayType(thisType))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+ // Arguments: [depth], func
+ if (typeof a === "number" && typeof b === "function")
+ return MapTypedSeqImpl(this, a, thisType, b);
+ else if (typeof a === "function")
+ return MapTypedSeqImpl(this, 1, thisType, a);
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+}
+
+// Warning: user exposed!
+function TypedObjectArrayReduce(a, b) {
+ // Arguments: func, [initial]
+ if (!IsObject(this) || !ObjectIsTypedObject(this))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+ var thisType = TypedObjectTypeDescr(this);
+ if (!TypeDescrIsArrayType(thisType))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+ if (a !== undefined && typeof a !== "function")
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+ var outputType = thisType.elementType;
+ return ReduceTypedSeqImpl(this, outputType, a, b);
+}
+
+// Warning: user exposed!
+function TypedObjectArrayFilter(func) {
+ // Arguments: predicate
+ if (!IsObject(this) || !ObjectIsTypedObject(this))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+ var thisType = TypedObjectTypeDescr(this);
+ if (!TypeDescrIsArrayType(thisType))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+ if (typeof func !== "function")
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+ return FilterTypedSeqImpl(this, func);
+}
+
+// should eventually become macros
+function NUM_BYTES(bits) {
+ return (bits + 7) >> 3;
+}
+function SET_BIT(data, index) {
+ var word = index >> 3;
+ var mask = 1 << (index & 0x7);
+ data[word] |= mask;
+}
+function GET_BIT(data, index) {
+ var word = index >> 3;
+ var mask = 1 << (index & 0x7);
+ return (data[word] & mask) != 0;
+}
+
+// Bug 956914: make performance-tuned variants tailored to 1, 2, and 3 dimensions.
+function BuildTypedSeqImpl(arrayType, len, depth, func) {
+ assert(IsObject(arrayType) && ObjectIsTypeDescr(arrayType), "Build called on non-type-object");
+
+ if (depth <= 0 || TO_INT32(depth) !== depth) {
+ // RangeError("bad depth")
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+ }
+
+ // For example, if we have as input
+ // ArrayType(ArrayType(T, 4), 5)
+ // and a depth of 2, we get
+ // grainType = T
+ // iterationSpace = [5, 4]
+ var {iterationSpace, grainType, totalLength} =
+ ComputeIterationSpace(arrayType, depth, len);
+
+ // Create a zeroed instance with no data
+ var result = new arrayType();
+
+ var indices = new List();
+ indices.length = depth;
+ for (var i = 0; i < depth; i++) {
+ indices[i] = 0;
+ }
+
+ var grainTypeIsComplex = !TypeDescrIsSimpleType(grainType);
+ var size = DESCR_SIZE(grainType);
+ var outOffset = 0;
+ for (i = 0; i < totalLength; i++) {
+ // Position out-pointer to point at &result[...indices], if appropriate.
+ var userOutPointer = TypedObjectGetOpaqueIf(grainType, result, outOffset,
+ grainTypeIsComplex);
+
+ // Invoke func(...indices, userOutPointer) and store the result
+ callFunction(std_Array_push, indices, userOutPointer);
+ var r = callFunction(std_Function_apply, func, undefined, indices);
+ callFunction(std_Array_pop, indices);
+ if (r !== undefined)
+ TypedObjectSet(grainType, result, outOffset, null, r); // result[...indices] = r;
+
+ // Increment indices.
+ IncrementIterationSpace(indices, iterationSpace);
+ outOffset += size;
+ }
+
+ return result;
+}
+
+function ComputeIterationSpace(arrayType, depth, len) {
+ assert(IsObject(arrayType) && ObjectIsTypeDescr(arrayType), "ComputeIterationSpace called on non-type-object");
+ assert(TypeDescrIsArrayType(arrayType), "ComputeIterationSpace called on non-array-type");
+ assert(depth > 0, "ComputeIterationSpace called on non-positive depth");
+ var iterationSpace = new List();
+ iterationSpace.length = depth;
+ iterationSpace[0] = len;
+ var totalLength = len;
+ var grainType = arrayType.elementType;
+
+ for (var i = 1; i < depth; i++) {
+ if (TypeDescrIsArrayType(grainType)) {
+ var grainLen = grainType.length;
+ iterationSpace[i] = grainLen;
+ totalLength *= grainLen;
+ grainType = grainType.elementType;
+ } else {
+ // RangeError("Depth "+depth+" too high");
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+ }
+ }
+ return { iterationSpace: iterationSpace,
+ grainType: grainType,
+ totalLength: totalLength };
+}
+
+function IncrementIterationSpace(indices, iterationSpace) {
+ // Increment something like
+ // [5, 5, 7, 8]
+ // in an iteration space of
+ // [9, 9, 9, 9]
+ // to
+ // [5, 5, 8, 0]
+
+ assert(indices.length === iterationSpace.length,
+ "indices dimension must equal iterationSpace dimension.");
+ var n = indices.length - 1;
+ while (true) {
+ indices[n] += 1;
+ if (indices[n] < iterationSpace[n])
+ return;
+
+ assert(indices[n] === iterationSpace[n],
+ "Components of indices must match those of iterationSpace.");
+ indices[n] = 0;
+ if (n == 0)
+ return;
+
+ n -= 1;
+ }
+}
+
+// Implements |from| method for untyped |inArray|. (Depth is implicitly 1 for untyped input.)
+function MapUntypedSeqImpl(inArray, outputType, maybeFunc) {
+ assert(IsObject(outputType), "1. Map/From called on non-object outputType");
+ assert(ObjectIsTypeDescr(outputType), "1. Map/From called on non-type-object outputType");
+ inArray = ToObject(inArray);
+ assert(TypeDescrIsArrayType(outputType), "Map/From called on non array-type outputType");
+
+ if (!IsCallable(maybeFunc))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, maybeFunc));
+ var func = maybeFunc;
+
+ // Skip check for compatible iteration spaces; any normal JS array
+ // is trivially compatible with any iteration space of depth 1.
+
+ var outLength = outputType.length;
+ var outGrainType = outputType.elementType;
+
+ // Create a zeroed instance with no data
+ var result = new outputType();
+
+ var outUnitSize = DESCR_SIZE(outGrainType);
+ var outGrainTypeIsComplex = !TypeDescrIsSimpleType(outGrainType);
+ var outOffset = 0;
+
+ // Core of map computation starts here (comparable to
+ // DoMapTypedSeqDepth1 and DoMapTypedSeqDepthN below).
+
+ for (var i = 0; i < outLength; i++) {
+ // In this loop, since depth is 1, "indices" denotes singleton array [i].
+
+ if (i in inArray) { // Check for holes (only needed for untyped case).
+ // Extract element value.
+ var element = inArray[i];
+
+ // Create out pointer to point at &array[...indices] for result array.
+ var out = TypedObjectGetOpaqueIf(outGrainType, result, outOffset,
+ outGrainTypeIsComplex);
+
+ // Invoke: var r = func(element, ...indices, collection, out);
+ var r = func(element, i, inArray, out);
+
+ if (r !== undefined)
+ TypedObjectSet(outGrainType, result, outOffset, null, r); // result[i] = r
+ }
+
+ // Update offset and (implicitly) increment indices.
+ outOffset += outUnitSize;
+ }
+
+ return result;
+}
+
+// Implements |map| and |from| methods for typed |inArray|.
+function MapTypedSeqImpl(inArray, depth, outputType, func) {
+ assert(IsObject(outputType) && ObjectIsTypeDescr(outputType), "2. Map/From called on non-type-object outputType");
+ assert(IsObject(inArray) && ObjectIsTypedObject(inArray), "Map/From called on non-object or untyped input array.");
+ assert(TypeDescrIsArrayType(outputType), "Map/From called on non array-type outputType");
+
+ if (depth <= 0 || TO_INT32(depth) !== depth)
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+ // Compute iteration space for input and output and check for compatibility.
+ var inputType = TypeOfTypedObject(inArray);
+ var {iterationSpace:inIterationSpace, grainType:inGrainType} =
+ ComputeIterationSpace(inputType, depth, inArray.length);
+ if (!IsObject(inGrainType) || !ObjectIsTypeDescr(inGrainType))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+ var {iterationSpace, grainType:outGrainType, totalLength} =
+ ComputeIterationSpace(outputType, depth, outputType.length);
+ for (var i = 0; i < depth; i++)
+ if (inIterationSpace[i] !== iterationSpace[i])
+ // TypeError("Incompatible iteration space in input and output type");
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+ // Create a zeroed instance with no data
+ var result = new outputType();
+
+ var inGrainTypeIsComplex = !TypeDescrIsSimpleType(inGrainType);
+ var outGrainTypeIsComplex = !TypeDescrIsSimpleType(outGrainType);
+
+ var inOffset = 0;
+ var outOffset = 0;
+
+ var isDepth1Simple = depth == 1 && !(inGrainTypeIsComplex || outGrainTypeIsComplex);
+
+ var inUnitSize = isDepth1Simple ? 0 : DESCR_SIZE(inGrainType);
+ var outUnitSize = isDepth1Simple ? 0 : DESCR_SIZE(outGrainType);
+
+ // Bug 956914: add additional variants for depth = 2, 3, etc.
+
+ function DoMapTypedSeqDepth1() {
+ for (var i = 0; i < totalLength; i++) {
+ // In this loop, since depth is 1, "indices" denotes singleton array [i].
+
+ // Prepare input element/handle and out pointer
+ var element = TypedObjectGet(inGrainType, inArray, inOffset);
+ var out = TypedObjectGetOpaqueIf(outGrainType, result, outOffset,
+ outGrainTypeIsComplex);
+
+ // Invoke: var r = func(element, ...indices, collection, out);
+ var r = func(element, i, inArray, out);
+ if (r !== undefined)
+ TypedObjectSet(outGrainType, result, outOffset, null, r); // result[i] = r
+
+ // Update offsets and (implicitly) increment indices.
+ inOffset += inUnitSize;
+ outOffset += outUnitSize;
+ }
+
+ return result;
+ }
+
+ function DoMapTypedSeqDepth1Simple(inArray, totalLength, func, result) {
+ for (var i = 0; i < totalLength; i++) {
+ var r = func(inArray[i], i, inArray, undefined);
+ if (r !== undefined)
+ result[i] = r;
+ }
+
+ return result;
+ }
+
+ function DoMapTypedSeqDepthN() {
+ // Simulate Uint32Array(depth) with a dumber (and more accessible)
+ // datastructure.
+ var indices = new List();
+ for (var i = 0; i < depth; i++)
+ callFunction(std_Array_push, indices, 0);
+
+ for (var i = 0; i < totalLength; i++) {
+ // Prepare input element and out pointer
+ var element = TypedObjectGet(inGrainType, inArray, inOffset);
+ var out = TypedObjectGetOpaqueIf(outGrainType, result, outOffset,
+ outGrainTypeIsComplex);
+
+ // Invoke: var r = func(element, ...indices, collection, out);
+ var args = [element];
+ callFunction(std_Function_apply, std_Array_push, args, indices);
+ callFunction(std_Array_push, args, inArray, out);
+ var r = callFunction(std_Function_apply, func, void 0, args);
+ if (r !== undefined)
+ TypedObjectSet(outGrainType, result, outOffset, null, r); // result[...indices] = r
+
+ // Update offsets and explicitly increment indices.
+ inOffset += inUnitSize;
+ outOffset += outUnitSize;
+ IncrementIterationSpace(indices, iterationSpace);
+ }
+
+ return result;
+ }
+
+ if (isDepth1Simple)
+ return DoMapTypedSeqDepth1Simple(inArray, totalLength, func, result);
+
+ if (depth == 1)
+ return DoMapTypedSeqDepth1();
+
+ return DoMapTypedSeqDepthN();
+}
+
+function ReduceTypedSeqImpl(array, outputType, func, initial) {
+ assert(IsObject(array) && ObjectIsTypedObject(array), "Reduce called on non-object or untyped input array.");
+ assert(IsObject(outputType) && ObjectIsTypeDescr(outputType), "Reduce called on non-type-object outputType");
+
+ var start, value;
+
+ if (initial === undefined && array.length < 1)
+ // RangeError("reduce requires array of length > 0")
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+ // FIXME bug 950106 Should reduce method supply an outptr handle?
+ // For now, reduce never supplies an outptr, regardless of outputType.
+
+ if (TypeDescrIsSimpleType(outputType)) {
+ if (initial === undefined) {
+ start = 1;
+ value = array[0];
+ } else {
+ start = 0;
+ value = outputType(initial);
+ }
+
+ for (var i = start; i < array.length; i++)
+ value = outputType(func(value, array[i]));
+
+ } else {
+ if (initial === undefined) {
+ start = 1;
+ value = new outputType(array[0]);
+ } else {
+ start = 0;
+ value = initial;
+ }
+
+ for (var i = start; i < array.length; i++)
+ value = func(value, array[i]);
+ }
+
+ return value;
+}
+
+function FilterTypedSeqImpl(array, func) {
+ assert(IsObject(array) && ObjectIsTypedObject(array), "Filter called on non-object or untyped input array.");
+ assert(typeof func === "function", "Filter called with non-function predicate");
+
+ var arrayType = TypeOfTypedObject(array);
+ if (!TypeDescrIsArrayType(arrayType))
+ ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+ var elementType = arrayType.elementType;
+ var flags = new Uint8Array(NUM_BYTES(array.length));
+ var count = 0;
+ var size = DESCR_SIZE(elementType);
+ var inOffset = 0;
+ for (var i = 0; i < array.length; i++) {
+ var v = TypedObjectGet(elementType, array, inOffset);
+ if (func(v, i, array)) {
+ SET_BIT(flags, i);
+ count++;
+ }
+ inOffset += size;
+ }
+
+ var AT = GetTypedObjectModule().ArrayType;
+
+ var resultType = new AT(elementType, count);
+ var result = new resultType();
+ for (var i = 0, j = 0; i < array.length; i++) {
+ if (GET_BIT(flags, i))
+ result[j++] = array[i];
+ }
+ return result;
+}
diff --git a/js/src/builtin/TypedObjectConstants.h b/js/src/builtin/TypedObjectConstants.h
new file mode 100644
index 000000000..41f30ab3e
--- /dev/null
+++ b/js/src/builtin/TypedObjectConstants.h
@@ -0,0 +1,121 @@
+/* -*- 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/. */
+
+// Specialized .h file to be used by both JS and C++ code.
+
+#ifndef builtin_TypedObjectConstants_h
+#define builtin_TypedObjectConstants_h
+
+///////////////////////////////////////////////////////////////////////////
+// Values to be returned by SetFromTypedArrayApproach
+
+#define JS_SETTYPEDARRAY_SAME_TYPE 0
+#define JS_SETTYPEDARRAY_OVERLAPPING 1
+#define JS_SETTYPEDARRAY_DISJOINT 2
+
+///////////////////////////////////////////////////////////////////////////
+// Slots for objects using the typed array layout
+
+#define JS_TYPEDARRAYLAYOUT_BUFFER_SLOT 0
+#define JS_TYPEDARRAYLAYOUT_LENGTH_SLOT 1
+#define JS_TYPEDARRAYLAYOUT_BYTEOFFSET_SLOT 2
+
+///////////////////////////////////////////////////////////////////////////
+// Slots and flags for ArrayBuffer objects
+
+#define JS_ARRAYBUFFER_FLAGS_SLOT 3
+
+#define JS_ARRAYBUFFER_DETACHED_FLAG 0x4
+
+///////////////////////////////////////////////////////////////////////////
+// Slots for typed prototypes
+
+#define JS_TYPROTO_SLOTS 0
+
+///////////////////////////////////////////////////////////////////////////
+// Slots for type objects
+//
+// Some slots apply to all type objects and some are specific to
+// particular kinds of type objects. For simplicity we use the same
+// number of slots no matter what kind of type descriptor we are
+// working with, even though this is mildly wasteful.
+
+// Slots on all type objects
+#define JS_DESCR_SLOT_KIND 0 // Atomized string representation
+#define JS_DESCR_SLOT_STRING_REPR 1 // Atomized string representation
+#define JS_DESCR_SLOT_ALIGNMENT 2 // Alignment in bytes
+#define JS_DESCR_SLOT_SIZE 3 // Size in bytes, else 0
+#define JS_DESCR_SLOT_OPAQUE 4 // Atomized string representation
+#define JS_DESCR_SLOT_TYPROTO 5 // Prototype for instances, if any
+#define JS_DESCR_SLOT_ARRAYPROTO 6 // Lazily created prototype for arrays
+#define JS_DESCR_SLOT_TRACE_LIST 7 // List of references for use in tracing
+
+// Slots on scalars, references, and SIMD objects
+#define JS_DESCR_SLOT_TYPE 8 // Type code
+
+// Slots on array descriptors
+#define JS_DESCR_SLOT_ARRAY_ELEM_TYPE 8
+#define JS_DESCR_SLOT_ARRAY_LENGTH 9
+
+// Slots on struct type objects
+#define JS_DESCR_SLOT_STRUCT_FIELD_NAMES 8
+#define JS_DESCR_SLOT_STRUCT_FIELD_TYPES 9
+#define JS_DESCR_SLOT_STRUCT_FIELD_OFFSETS 10
+
+// Maximum number of slots for any descriptor
+#define JS_DESCR_SLOTS 11
+
+// These constants are for use exclusively in JS code. In C++ code,
+// prefer TypeRepresentation::Scalar etc, which allows you to
+// write a switch which will receive a warning if you omit a case.
+#define JS_TYPEREPR_SCALAR_KIND 1
+#define JS_TYPEREPR_REFERENCE_KIND 2
+#define JS_TYPEREPR_STRUCT_KIND 3
+#define JS_TYPEREPR_ARRAY_KIND 4
+#define JS_TYPEREPR_SIMD_KIND 5
+
+// These constants are for use exclusively in JS code. In C++ code,
+// prefer Scalar::Int8 etc, which allows you to write a switch which will
+// receive a warning if you omit a case.
+#define JS_SCALARTYPEREPR_INT8 0
+#define JS_SCALARTYPEREPR_UINT8 1
+#define JS_SCALARTYPEREPR_INT16 2
+#define JS_SCALARTYPEREPR_UINT16 3
+#define JS_SCALARTYPEREPR_INT32 4
+#define JS_SCALARTYPEREPR_UINT32 5
+#define JS_SCALARTYPEREPR_FLOAT32 6
+#define JS_SCALARTYPEREPR_FLOAT64 7
+#define JS_SCALARTYPEREPR_UINT8_CLAMPED 8
+#define JS_SCALARTYPEREPR_FLOAT32X4 11
+#define JS_SCALARTYPEREPR_INT8X16 12
+#define JS_SCALARTYPEREPR_INT16X8 13
+#define JS_SCALARTYPEREPR_INT32X4 14
+
+// These constants are for use exclusively in JS code. In C++ code,
+// prefer ReferenceTypeRepresentation::TYPE_ANY etc, which allows
+// you to write a switch which will receive a warning if you omit a
+// case.
+#define JS_REFERENCETYPEREPR_ANY 0
+#define JS_REFERENCETYPEREPR_OBJECT 1
+#define JS_REFERENCETYPEREPR_STRING 2
+
+// These constants are for use exclusively in JS code. In C++ code, prefer
+// SimdType::Int32x4 etc, since that allows you to write a switch which will
+// receive a warning if you omit a case.
+#define JS_SIMDTYPEREPR_INT8X16 0
+#define JS_SIMDTYPEREPR_INT16X8 1
+#define JS_SIMDTYPEREPR_INT32X4 2
+#define JS_SIMDTYPEREPR_UINT8X16 3
+#define JS_SIMDTYPEREPR_UINT16X8 4
+#define JS_SIMDTYPEREPR_UINT32X4 5
+#define JS_SIMDTYPEREPR_FLOAT32X4 6
+#define JS_SIMDTYPEREPR_FLOAT64X2 7
+#define JS_SIMDTYPEREPR_BOOL8X16 8
+#define JS_SIMDTYPEREPR_BOOL16X8 9
+#define JS_SIMDTYPEREPR_BOOL32X4 10
+#define JS_SIMDTYPEREPR_BOOL64X2 11
+
+#endif
diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js
new file mode 100644
index 000000000..bfb1fe7f4
--- /dev/null
+++ b/js/src/builtin/Utilities.js
@@ -0,0 +1,227 @@
+/* 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/. */
+
+/*jshint bitwise: true, camelcase: false, curly: false, eqeqeq: true,
+ es5: true, forin: true, immed: true, indent: 4, latedef: false,
+ newcap: false, noarg: true, noempty: true, nonew: true,
+ plusplus: false, quotmark: false, regexp: true, undef: true,
+ unused: false, strict: false, trailing: true,
+*/
+
+/*global ToObject: false, ToInteger: false, IsCallable: false,
+ ThrowRangeError: false, ThrowTypeError: false,
+ AssertionFailed: false,
+ MakeConstructible: false, DecompileArg: false,
+ RuntimeDefaultLocale: false,
+ NewDenseArray: false,
+ Dump: false,
+ callFunction: false,
+ TO_UINT32: false,
+ JSMSG_NOT_FUNCTION: false, JSMSG_MISSING_FUN_ARG: false,
+ JSMSG_EMPTY_ARRAY_REDUCE: false, JSMSG_CANT_CONVERT_TO: false,
+*/
+
+#include "SelfHostingDefines.h"
+
+// Assertions and debug printing, defined here instead of in the header above
+// to make `assert` invisible to C++.
+#ifdef DEBUG
+#define assert(b, info) if (!(b)) AssertionFailed(__FILE__ + ":" + __LINE__ + ": " + info)
+#define dbg(msg) DumpMessage(callFunction(std_Array_pop, \
+ StringSplitString(__FILE__, '/')) \
+ + '#' + __LINE__ + ': ' + msg)
+#else
+#define assert(b, info) // Elided assertion.
+#define dbg(msg) // Elided debugging output.
+#endif
+
+// All C++-implemented standard builtins library functions used in self-hosted
+// code are installed via the std_functions JSFunctionSpec[] in
+// SelfHosting.cpp.
+//
+// Do not create an alias to a self-hosted builtin, otherwise it will be cloned
+// twice.
+//
+// WeakMap is a bare constructor without properties or methods.
+var std_WeakMap = WeakMap;
+// StopIteration is a bare constructor without properties or methods.
+var std_StopIteration = StopIteration;
+
+
+/********** List specification type **********/
+
+/* Spec: ECMAScript Language Specification, 5.1 edition, 8.8 */
+function List() {
+ this.length = 0;
+}
+MakeConstructible(List, {__proto__: null});
+
+
+/********** Record specification type **********/
+
+
+/* Spec: ECMAScript Internationalization API Specification, draft, 5 */
+function Record() {
+ return std_Object_create(null);
+}
+MakeConstructible(Record, {});
+
+
+/********** Abstract operations defined in ECMAScript Language Specification **********/
+
+
+/* Spec: ECMAScript Language Specification, 5.1 edition, 8.12.6 and 11.8.7 */
+function HasProperty(o, p) {
+ return p in o;
+}
+
+
+/* Spec: ECMAScript Language Specification, 5.1 edition, 9.2 and 11.4.9 */
+function ToBoolean(v) {
+ return !!v;
+}
+
+
+/* Spec: ECMAScript Language Specification, 5.1 edition, 9.3 and 11.4.6 */
+function ToNumber(v) {
+ return +v;
+}
+
+
+// ES6 7.2.1 (previously, ES5 9.10 under the name "CheckObjectCoercible").
+function RequireObjectCoercible(v) {
+ if (v === undefined || v === null)
+ ThrowTypeError(JSMSG_CANT_CONVERT_TO, ToString(v), "object");
+}
+
+/* Spec: ECMAScript Draft, 6 edition May 22, 2014, 7.1.15 */
+function ToLength(v) {
+ v = ToInteger(v);
+
+ if (v <= 0)
+ return 0;
+
+ // Math.pow(2, 53) - 1 = 0x1fffffffffffff
+ return std_Math_min(v, 0x1fffffffffffff);
+}
+
+/* Spec: ECMAScript Draft, 6th edition Oct 14, 2014, 7.2.4 */
+function SameValueZero(x, y) {
+ return x === y || (x !== x && y !== y);
+}
+
+// ES 2017 draft (April 6, 2016) 7.3.9
+function GetMethod(V, P) {
+ // Step 1.
+ assert(IsPropertyKey(P), "Invalid property key");
+
+ // Step 2.
+ var func = V[P];
+
+ // Step 3.
+ if (func === undefined || func === null)
+ return undefined;
+
+ // Step 4.
+ if (!IsCallable(func))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, typeof func);
+
+ // Step 5.
+ return func;
+}
+
+/* Spec: ECMAScript Draft, 6th edition Dec 24, 2014, 7.2.7 */
+function IsPropertyKey(argument) {
+ var type = typeof argument;
+ return type === "string" || type === "symbol";
+}
+
+/* Spec: ECMAScript Draft, 6th edition Dec 24, 2014, 7.4.1 */
+function GetIterator(obj, method) {
+ // Steps 1-2.
+ if (arguments.length === 1)
+ method = GetMethod(obj, std_iterator);
+
+ // Steps 3-4.
+ var iterator = callContentFunction(method, obj);
+
+ // Step 5.
+ if (!IsObject(iterator))
+ ThrowTypeError(JSMSG_NOT_ITERATOR, ToString(iterator));
+
+ // Step 6.
+ return iterator;
+}
+
+var _builtinCtorsCache = {__proto__: null};
+
+function GetBuiltinConstructor(builtinName) {
+ var ctor = _builtinCtorsCache[builtinName] ||
+ (_builtinCtorsCache[builtinName] = GetBuiltinConstructorImpl(builtinName));
+ assert(ctor, `No builtin with name "${builtinName}" found`);
+ return ctor;
+}
+
+function GetBuiltinPrototype(builtinName) {
+ return (_builtinCtorsCache[builtinName] || GetBuiltinConstructor(builtinName)).prototype;
+}
+
+// ES 2016 draft Mar 25, 2016 7.3.20.
+function SpeciesConstructor(obj, defaultConstructor) {
+ // Step 1.
+ assert(IsObject(obj), "not passed an object");
+
+ // Step 2.
+ var ctor = obj.constructor;
+
+ // Step 3.
+ if (ctor === undefined)
+ return defaultConstructor;
+
+ // Step 4.
+ if (!IsObject(ctor))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "object's 'constructor' property");
+
+ // Steps 5.
+ var s = ctor[std_species];
+
+ // Step 6.
+ if (s === undefined || s === null)
+ return defaultConstructor;
+
+ // Step 7.
+ if (IsConstructor(s))
+ return s;
+
+ // Step 8.
+ ThrowTypeError(JSMSG_NOT_CONSTRUCTOR, "@@species property of object's constructor");
+}
+
+function GetTypeError(msg) {
+ try {
+ FUN_APPLY(ThrowTypeError, undefined, arguments);
+ } catch (e) {
+ return e;
+ }
+ assert(false, "the catch block should've returned from this function.");
+}
+
+function GetInternalError(msg) {
+ try {
+ FUN_APPLY(ThrowInternalError, undefined, arguments);
+ } catch (e) {
+ return e;
+ }
+ assert(false, "the catch block should've returned from this function.");
+}
+
+// To be used when a function is required but calling it shouldn't do anything.
+function NullFunction() {}
+
+/*************************************** Testing functions ***************************************/
+function outer() {
+ return function inner() {
+ return "foo";
+ }
+}
diff --git a/js/src/builtin/WeakMap.js b/js/src/builtin/WeakMap.js
new file mode 100644
index 000000000..066a72bfe
--- /dev/null
+++ b/js/src/builtin/WeakMap.js
@@ -0,0 +1,49 @@
+/* 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/. */
+
+// ES2017 draft rev 0e10c9f29fca1385980c08a7d5e7bb3eb775e2e4
+// 23.3.1.1 WeakMap, steps 6-8
+function WeakMapConstructorInit(iterable) {
+ var map = this;
+
+ // Step 6.a.
+ var adder = map.set;
+
+ // Step 6.b.
+ if (!IsCallable(adder))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, typeof adder);
+
+ // Step 6.c.
+ var iterFn = iterable[std_iterator];
+ if (!IsCallable(iterFn))
+ ThrowTypeError(JSMSG_NOT_ITERABLE, DecompileArg(0, iterable));
+
+ var iter = callContentFunction(iterFn, iterable);
+ if (!IsObject(iter))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof iter);
+
+ // Step 7 (not applicable).
+
+ // Step 8.
+ while (true) {
+ // Step 8.a.
+ var next = callContentFunction(iter.next, iter);
+ if (!IsObject(next))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof next);
+
+ // Step 8.b.
+ if (next.done)
+ return;
+
+ // Step 8.c.
+ var nextItem = next.value;
+
+ // Step 8.d.
+ if (!IsObject(nextItem))
+ ThrowTypeError(JSMSG_INVALID_MAP_ITERABLE, "WeakMap");
+
+ // Steps 8.e-j.
+ callContentFunction(adder, map, nextItem[0], nextItem[1]);
+ }
+}
diff --git a/js/src/builtin/WeakMapObject.cpp b/js/src/builtin/WeakMapObject.cpp
new file mode 100644
index 000000000..555b9e03a
--- /dev/null
+++ b/js/src/builtin/WeakMapObject.cpp
@@ -0,0 +1,390 @@
+/* -*- 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 "builtin/WeakMapObject.h"
+
+#include "jsapi.h"
+#include "jscntxt.h"
+
+#include "vm/SelfHosting.h"
+
+#include "vm/Interpreter-inl.h"
+
+using namespace js;
+using namespace js::gc;
+
+MOZ_ALWAYS_INLINE bool
+IsWeakMap(HandleValue v)
+{
+ return v.isObject() && v.toObject().is<WeakMapObject>();
+}
+
+MOZ_ALWAYS_INLINE bool
+WeakMap_has_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(IsWeakMap(args.thisv()));
+
+ if (!args.get(0).isObject()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
+ JSObject* key = &args[0].toObject();
+ if (map->has(key)) {
+ args.rval().setBoolean(true);
+ return true;
+ }
+ }
+
+ args.rval().setBoolean(false);
+ return true;
+}
+
+bool
+js::WeakMap_has(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsWeakMap, WeakMap_has_impl>(cx, args);
+}
+
+MOZ_ALWAYS_INLINE bool
+WeakMap_get_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(IsWeakMap(args.thisv()));
+
+ if (!args.get(0).isObject()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
+ JSObject* key = &args[0].toObject();
+ if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
+ args.rval().set(ptr->value());
+ return true;
+ }
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+bool
+js::WeakMap_get(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsWeakMap, WeakMap_get_impl>(cx, args);
+}
+
+MOZ_ALWAYS_INLINE bool
+WeakMap_delete_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(IsWeakMap(args.thisv()));
+
+ if (!args.get(0).isObject()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
+ JSObject* key = &args[0].toObject();
+ if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
+ map->remove(ptr);
+ args.rval().setBoolean(true);
+ return true;
+ }
+ }
+
+ args.rval().setBoolean(false);
+ return true;
+}
+
+bool
+js::WeakMap_delete(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsWeakMap, WeakMap_delete_impl>(cx, args);
+}
+
+static bool
+TryPreserveReflector(JSContext* cx, HandleObject obj)
+{
+ if (obj->getClass()->isWrappedNative() ||
+ obj->getClass()->isDOMClass() ||
+ (obj->is<ProxyObject>() &&
+ obj->as<ProxyObject>().handler()->family() == GetDOMProxyHandlerFamily()))
+ {
+ MOZ_ASSERT(cx->runtime()->preserveWrapperCallback);
+ if (!cx->runtime()->preserveWrapperCallback(cx, obj)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_WEAKMAP_KEY);
+ return false;
+ }
+ }
+ return true;
+}
+
+static MOZ_ALWAYS_INLINE bool
+SetWeakMapEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
+ HandleObject key, HandleValue value)
+{
+ ObjectValueMap* map = mapObj->getMap();
+ if (!map) {
+ auto newMap = cx->make_unique<ObjectValueMap>(cx, mapObj.get());
+ if (!newMap)
+ return false;
+ if (!newMap->init()) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ map = newMap.release();
+ mapObj->setPrivate(map);
+ }
+
+ // Preserve wrapped native keys to prevent wrapper optimization.
+ if (!TryPreserveReflector(cx, key))
+ return false;
+
+ if (JSWeakmapKeyDelegateOp op = key->getClass()->extWeakmapKeyDelegateOp()) {
+ RootedObject delegate(cx, op(key));
+ if (delegate && !TryPreserveReflector(cx, delegate))
+ return false;
+ }
+
+ MOZ_ASSERT(key->compartment() == mapObj->compartment());
+ MOZ_ASSERT_IF(value.isObject(), value.toObject().compartment() == mapObj->compartment());
+ if (!map->put(key, value)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ return true;
+}
+
+MOZ_ALWAYS_INLINE bool
+WeakMap_set_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(IsWeakMap(args.thisv()));
+
+ if (!args.get(0).isObject()) {
+ UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), nullptr);
+ if (!bytes)
+ return false;
+ JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
+ bytes.get());
+ return false;
+ }
+
+ RootedObject key(cx, &args[0].toObject());
+ Rooted<JSObject*> thisObj(cx, &args.thisv().toObject());
+ Rooted<WeakMapObject*> map(cx, &thisObj->as<WeakMapObject>());
+
+ if (!SetWeakMapEntryInternal(cx, map, key, args.get(1)))
+ return false;
+ args.rval().set(args.thisv());
+ return true;
+}
+
+bool
+js::WeakMap_set(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsWeakMap, WeakMap_set_impl>(cx, args);
+}
+
+JS_FRIEND_API(bool)
+JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHandleObject ret)
+{
+ RootedObject obj(cx, objArg);
+ obj = UncheckedUnwrap(obj);
+ if (!obj || !obj->is<WeakMapObject>()) {
+ ret.set(nullptr);
+ return true;
+ }
+ RootedObject arr(cx, NewDenseEmptyArray(cx));
+ if (!arr)
+ return false;
+ ObjectValueMap* map = obj->as<WeakMapObject>().getMap();
+ if (map) {
+ // Prevent GC from mutating the weakmap while iterating.
+ AutoSuppressGC suppress(cx);
+ for (ObjectValueMap::Base::Range r = map->all(); !r.empty(); r.popFront()) {
+ JS::ExposeObjectToActiveJS(r.front().key());
+ RootedObject key(cx, r.front().key());
+ if (!cx->compartment()->wrap(cx, &key))
+ return false;
+ if (!NewbornArrayPush(cx, arr, ObjectValue(*key)))
+ return false;
+ }
+ }
+ ret.set(arr);
+ return true;
+}
+
+static void
+WeakMap_mark(JSTracer* trc, JSObject* obj)
+{
+ if (ObjectValueMap* map = obj->as<WeakMapObject>().getMap())
+ map->trace(trc);
+}
+
+static void
+WeakMap_finalize(FreeOp* fop, JSObject* obj)
+{
+ MOZ_ASSERT(fop->maybeOffMainThread());
+ if (ObjectValueMap* map = obj->as<WeakMapObject>().getMap()) {
+#ifdef DEBUG
+ map->~ObjectValueMap();
+ memset(static_cast<void*>(map), 0xdc, sizeof(*map));
+ fop->free_(map);
+#else
+ fop->delete_(map);
+#endif
+ }
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::NewWeakMapObject(JSContext* cx)
+{
+ return NewBuiltinClassInstance(cx, &WeakMapObject::class_);
+}
+
+JS_PUBLIC_API(bool)
+JS::IsWeakMapObject(JSObject* obj)
+{
+ return obj->is<WeakMapObject>();
+}
+
+JS_PUBLIC_API(bool)
+JS::GetWeakMapEntry(JSContext* cx, HandleObject mapObj, HandleObject key,
+ MutableHandleValue rval)
+{
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, key);
+ rval.setUndefined();
+ ObjectValueMap* map = mapObj->as<WeakMapObject>().getMap();
+ if (!map)
+ return true;
+ if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
+ // Read barrier to prevent an incorrectly gray value from escaping the
+ // weak map. See the comment before UnmarkGrayChildren in gc/Marking.cpp
+ ExposeValueToActiveJS(ptr->value().get());
+ rval.set(ptr->value());
+ }
+ return true;
+}
+
+JS_PUBLIC_API(bool)
+JS::SetWeakMapEntry(JSContext* cx, HandleObject mapObj, HandleObject key,
+ HandleValue val)
+{
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, key, val);
+ Rooted<WeakMapObject*> rootedMap(cx, &mapObj->as<WeakMapObject>());
+ return SetWeakMapEntryInternal(cx, rootedMap, key, val);
+}
+
+static bool
+WeakMap_construct(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // ES6 draft rev 31 (15 Jan 2015) 23.3.1.1 step 1.
+ if (!ThrowIfNotConstructing(cx, args, "WeakMap"))
+ return false;
+
+ RootedObject newTarget(cx, &args.newTarget().toObject());
+ RootedObject obj(cx, CreateThis(cx, &WeakMapObject::class_, newTarget));
+ if (!obj)
+ return false;
+
+ // Steps 5-6, 11.
+ if (!args.get(0).isNullOrUndefined()) {
+ FixedInvokeArgs<1> args2(cx);
+ args2[0].set(args[0]);
+
+ RootedValue thisv(cx, ObjectValue(*obj));
+ if (!CallSelfHostedFunction(cx, cx->names().WeakMapConstructorInit, thisv, args2, args2.rval()))
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static const ClassOps WeakMapObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ WeakMap_finalize,
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ nullptr, /* construct */
+ WeakMap_mark
+};
+
+const Class WeakMapObject::class_ = {
+ "WeakMap",
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_WeakMap) |
+ JSCLASS_BACKGROUND_FINALIZE,
+ &WeakMapObjectClassOps
+};
+
+static const JSFunctionSpec weak_map_methods[] = {
+ JS_FN("has", WeakMap_has, 1, 0),
+ JS_FN("get", WeakMap_get, 1, 0),
+ JS_FN("delete", WeakMap_delete, 1, 0),
+ JS_FN("set", WeakMap_set, 2, 0),
+ JS_FS_END
+};
+
+static JSObject*
+InitWeakMapClass(JSContext* cx, HandleObject obj, bool defineMembers)
+{
+ MOZ_ASSERT(obj->isNative());
+
+ Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+
+ RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!proto)
+ return nullptr;
+
+ RootedFunction ctor(cx, global->createConstructor(cx, WeakMap_construct,
+ cx->names().WeakMap, 0));
+ if (!ctor)
+ return nullptr;
+
+ if (!LinkConstructorAndPrototype(cx, ctor, proto))
+ return nullptr;
+
+ if (defineMembers) {
+ if (!DefinePropertiesAndFunctions(cx, proto, nullptr, weak_map_methods))
+ return nullptr;
+ if (!DefineToStringTag(cx, proto, cx->names().WeakMap))
+ return nullptr;
+ }
+
+ if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakMap, ctor, proto))
+ return nullptr;
+ return proto;
+}
+
+JSObject*
+js::InitWeakMapClass(JSContext* cx, HandleObject obj)
+{
+ return InitWeakMapClass(cx, obj, true);
+}
+
+JSObject*
+js::InitBareWeakMapCtor(JSContext* cx, HandleObject obj)
+{
+ return InitWeakMapClass(cx, obj, false);
+}
+
diff --git a/js/src/builtin/WeakMapObject.h b/js/src/builtin/WeakMapObject.h
new file mode 100644
index 000000000..c9b95cea3
--- /dev/null
+++ b/js/src/builtin/WeakMapObject.h
@@ -0,0 +1,25 @@
+/* -*- 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/. */
+
+#ifndef builtin_WeakMapObject_h
+#define builtin_WeakMapObject_h
+
+#include "jsobj.h"
+#include "jsweakmap.h"
+
+namespace js {
+
+class WeakMapObject : public NativeObject
+{
+ public:
+ static const Class class_;
+
+ ObjectValueMap* getMap() { return static_cast<ObjectValueMap*>(getPrivate()); }
+};
+
+} // namespace js
+
+#endif /* builtin_WeakMapObject_h */
diff --git a/js/src/builtin/WeakSet.js b/js/src/builtin/WeakSet.js
new file mode 100644
index 000000000..eb7c2378f
--- /dev/null
+++ b/js/src/builtin/WeakSet.js
@@ -0,0 +1,108 @@
+/* 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/. */
+
+// ES2017 draft rev 0e10c9f29fca1385980c08a7d5e7bb3eb775e2e4
+// 23.4.1.1 WeakSet, steps 6-8
+function WeakSetConstructorInit(iterable) {
+ var set = this;
+
+ // Step 6.a.
+ var adder = set.add;
+
+ // Step 6.b.
+ if (!IsCallable(adder))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, typeof adder);
+
+ // Step 6.c.
+ var iterFn = iterable[std_iterator];
+ if (!IsCallable(iterFn))
+ ThrowTypeError(JSMSG_NOT_ITERABLE, DecompileArg(0, iterable));
+
+ var iter = callContentFunction(iterFn, iterable);
+ if (!IsObject(iter))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof iter);
+
+ // Step 7 (not applicable).
+
+ // Step 8.
+ while (true) {
+ // Step 8.a.
+ var next = callContentFunction(iter.next, iter);
+ if (!IsObject(next))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof next);
+
+ // Step 8.b.
+ if (next.done)
+ return;
+
+ // Step 8.c.
+ var nextValue = next.value;
+
+ // Steps 8.d-e.
+ callContentFunction(adder, set, nextValue);
+ }
+}
+
+// 23.4.3.1
+function WeakSet_add(value) {
+ // Steps 1-3.
+ var S = this;
+ if (!IsObject(S) || !IsWeakSet(S))
+ return callFunction(CallWeakSetMethodIfWrapped, this, value, "WeakSet_add");
+
+ // Step 4.,6.
+ let entries = UnsafeGetReservedSlot(this, WEAKSET_MAP_SLOT);
+ if (!entries)
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "add", typeof S);
+
+ // Step 5.
+ if (!IsObject(value))
+ ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, DecompileArg(0, value));
+
+ // Steps 7-8.
+ callFunction(std_WeakMap_set, entries, value, true);
+
+ // Step 8.
+ return S;
+}
+
+// 23.4.3.3
+function WeakSet_delete(value) {
+ // Steps 1-3.
+ var S = this;
+ if (!IsObject(S) || !IsWeakSet(S))
+ return callFunction(CallWeakSetMethodIfWrapped, this, value, "WeakSet_delete");
+
+ // Step 4.,6.
+ let entries = UnsafeGetReservedSlot(this, WEAKSET_MAP_SLOT);
+ if (!entries)
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "delete", typeof S);
+
+ // Step 5.
+ if (!IsObject(value))
+ return false;
+
+ // Steps 7-8.
+ return callFunction(std_WeakMap_delete, entries, value);
+}
+
+// 23.4.3.4
+function WeakSet_has(value) {
+ // Steps 1-3.
+ var S = this;
+ if (!IsObject(S) || !IsWeakSet(S))
+ return callFunction(CallWeakSetMethodIfWrapped, this, value, "WeakSet_has");
+
+ // Step 4-5.
+ let entries = UnsafeGetReservedSlot(this, WEAKSET_MAP_SLOT);
+ if (!entries)
+ ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "has", typeof S);
+
+ // Step 6.
+ if (!IsObject(value))
+ return false;
+
+ // Steps 7-8.
+ return callFunction(std_WeakMap_has, entries, value);
+}
diff --git a/js/src/builtin/WeakSetObject.cpp b/js/src/builtin/WeakSetObject.cpp
new file mode 100644
index 000000000..7ea3f2fef
--- /dev/null
+++ b/js/src/builtin/WeakSetObject.cpp
@@ -0,0 +1,170 @@
+/* -*- 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 "builtin/WeakSetObject.h"
+
+#include "jsapi.h"
+#include "jscntxt.h"
+#include "jsiter.h"
+
+#include "builtin/MapObject.h"
+#include "builtin/SelfHostingDefines.h"
+#include "builtin/WeakMapObject.h"
+#include "vm/GlobalObject.h"
+#include "vm/SelfHosting.h"
+
+#include "jsobjinlines.h"
+
+#include "vm/Interpreter-inl.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+const Class WeakSetObject::class_ = {
+ "WeakSet",
+ JSCLASS_HAS_CACHED_PROTO(JSProto_WeakSet) |
+ JSCLASS_HAS_RESERVED_SLOTS(WeakSetObject::RESERVED_SLOTS)
+};
+
+const JSPropertySpec WeakSetObject::properties[] = {
+ JS_PS_END
+};
+
+const JSFunctionSpec WeakSetObject::methods[] = {
+ JS_SELF_HOSTED_FN("add", "WeakSet_add", 1, 0),
+ JS_SELF_HOSTED_FN("delete", "WeakSet_delete", 1, 0),
+ JS_SELF_HOSTED_FN("has", "WeakSet_has", 1, 0),
+ JS_FS_END
+};
+
+JSObject*
+WeakSetObject::initClass(JSContext* cx, JSObject* obj)
+{
+ Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+ RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!proto)
+ return nullptr;
+
+ Rooted<JSFunction*> ctor(cx, global->createConstructor(cx, construct, ClassName(JSProto_WeakSet, cx), 0));
+ if (!ctor ||
+ !LinkConstructorAndPrototype(cx, ctor, proto) ||
+ !DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
+ !DefineToStringTag(cx, proto, cx->names().WeakSet) ||
+ !GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakSet, ctor, proto))
+ {
+ return nullptr;
+ }
+ return proto;
+}
+
+WeakSetObject*
+WeakSetObject::create(JSContext* cx, HandleObject proto /* = nullptr */)
+{
+ RootedObject map(cx, NewBuiltinClassInstance<WeakMapObject>(cx));
+ if (!map)
+ return nullptr;
+
+ WeakSetObject* obj = NewObjectWithClassProto<WeakSetObject>(cx, proto);
+ if (!obj)
+ return nullptr;
+
+ obj->setReservedSlot(WEAKSET_MAP_SLOT, ObjectValue(*map));
+ return obj;
+}
+
+bool
+WeakSetObject::isBuiltinAdd(HandleValue add, JSContext* cx)
+{
+ JSFunction* addFn;
+ return IsFunctionObject(add, &addFn) && IsSelfHostedFunctionWithName(addFn, cx->names().WeakSet_add);
+}
+
+bool
+WeakSetObject::construct(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Based on our "Set" implementation instead of the more general ES6 steps.
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "WeakSet"))
+ return false;
+
+ RootedObject proto(cx);
+ RootedObject newTarget(cx, &args.newTarget().toObject());
+ if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+ return false;
+
+ Rooted<WeakSetObject*> obj(cx, WeakSetObject::create(cx, proto));
+ if (!obj)
+ return false;
+
+ if (!args.get(0).isNullOrUndefined()) {
+ RootedValue iterable(cx, args[0]);
+ bool optimized = false;
+ if (!IsOptimizableInitForSet<GlobalObject::getOrCreateWeakSetPrototype, isBuiltinAdd>(cx, obj, iterable, &optimized))
+ return false;
+
+ if (optimized) {
+ RootedValue keyVal(cx);
+ RootedObject keyObject(cx);
+ RootedValue placeholder(cx, BooleanValue(true));
+ RootedObject map(cx, &obj->getReservedSlot(WEAKSET_MAP_SLOT).toObject());
+ RootedArrayObject array(cx, &iterable.toObject().as<ArrayObject>());
+ for (uint32_t index = 0; index < array->getDenseInitializedLength(); ++index) {
+ keyVal.set(array->getDenseElement(index));
+ MOZ_ASSERT(!keyVal.isMagic(JS_ELEMENTS_HOLE));
+
+ if (keyVal.isPrimitive()) {
+ UniqueChars bytes =
+ DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, keyVal, nullptr);
+ if (!bytes)
+ return false;
+ JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
+ JSMSG_NOT_NONNULL_OBJECT, bytes.get());
+ return false;
+ }
+
+ keyObject = &keyVal.toObject();
+ if (!SetWeakMapEntry(cx, map, keyObject, placeholder))
+ return false;
+ }
+ } else {
+ FixedInvokeArgs<1> args2(cx);
+ args2[0].set(args[0]);
+
+ RootedValue thisv(cx, ObjectValue(*obj));
+ if (!CallSelfHostedFunction(cx, cx->names().WeakSetConstructorInit, thisv, args2, args2.rval()))
+ return false;
+ }
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+
+JSObject*
+js::InitWeakSetClass(JSContext* cx, HandleObject obj)
+{
+ return WeakSetObject::initClass(cx, obj);
+}
+
+JS_FRIEND_API(bool)
+JS_NondeterministicGetWeakSetKeys(JSContext* cx, HandleObject objArg, MutableHandleObject ret)
+{
+ RootedObject obj(cx, objArg);
+ obj = UncheckedUnwrap(obj);
+ if (!obj || !obj->is<WeakSetObject>()) {
+ ret.set(nullptr);
+ return true;
+ }
+
+ Rooted<WeakSetObject*> weakset(cx, &obj->as<WeakSetObject>());
+ if (!weakset)
+ return false;
+
+ RootedObject map(cx, &weakset->getReservedSlot(WEAKSET_MAP_SLOT).toObject());
+ return JS_NondeterministicGetWeakMapKeys(cx, map, ret);
+}
diff --git a/js/src/builtin/WeakSetObject.h b/js/src/builtin/WeakSetObject.h
new file mode 100644
index 000000000..0a6ff33f3
--- /dev/null
+++ b/js/src/builtin/WeakSetObject.h
@@ -0,0 +1,37 @@
+/* -*- 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/. */
+
+#ifndef builtin_WeakSetObject_h
+#define builtin_WeakSetObject_h
+
+#include "vm/NativeObject.h"
+
+namespace js {
+
+class WeakSetObject : public NativeObject
+{
+ public:
+ static const unsigned RESERVED_SLOTS = 1;
+
+ static JSObject* initClass(JSContext* cx, JSObject* obj);
+ static const Class class_;
+
+ private:
+ static const JSPropertySpec properties[];
+ static const JSFunctionSpec methods[];
+
+ static WeakSetObject* create(JSContext* cx, HandleObject proto = nullptr);
+ static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
+
+ static bool isBuiltinAdd(HandleValue add, JSContext* cx);
+};
+
+extern JSObject*
+InitWeakSetClass(JSContext* cx, HandleObject obj);
+
+} // namespace js
+
+#endif /* builtin_WeakSetObject_h */
diff --git a/js/src/builtin/embedjs.py b/js/src/builtin/embedjs.py
new file mode 100644
index 000000000..c642aebd5
--- /dev/null
+++ b/js/src/builtin/embedjs.py
@@ -0,0 +1,159 @@
+# 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/.
+#
+# ToCAsciiArray and ToCArray are from V8's js2c.py.
+#
+# Copyright 2012 the V8 project authors. All rights reserved.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# This utility converts JS files containing self-hosted builtins into a C
+# header file that can be embedded into SpiderMonkey.
+#
+# It uses the C preprocessor to process its inputs.
+
+from __future__ import with_statement
+import re, sys, os, subprocess
+import shlex
+import which
+import buildconfig
+
+def ToCAsciiArray(lines):
+ result = []
+ for chr in lines:
+ value = ord(chr)
+ assert value < 128
+ result.append(str(value))
+ return ", ".join(result)
+
+def ToCArray(lines):
+ result = []
+ for chr in lines:
+ result.append(str(ord(chr)))
+ return ", ".join(result)
+
+HEADER_TEMPLATE = """\
+/* 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/. */
+
+namespace js {
+namespace %(namespace)s {
+ static const %(sources_type)s data[] = { %(sources_data)s };
+
+ static const %(sources_type)s * const %(sources_name)s = reinterpret_cast<const %(sources_type)s *>(data);
+
+ uint32_t GetCompressedSize() {
+ return %(compressed_total_length)i;
+ }
+
+ uint32_t GetRawScriptsSize() {
+ return %(raw_total_length)i;
+ }
+} // selfhosted
+} // js
+"""
+
+def embed(cxx, preprocessorOption, msgs, sources, c_out, js_out, namespace, env):
+ combinedSources = '\n'.join([msgs] + ['#include "%(s)s"' % { 's': source } for source in sources])
+ args = ['-D%(k)s=%(v)s' % { 'k': k, 'v': env[k] } for k in env]
+ preprocessed = preprocess(cxx, preprocessorOption, combinedSources, args)
+ processed = '\n'.join([line for line in preprocessed.splitlines() if \
+ (line.strip() and not line.startswith('#'))])
+
+ js_out.write(processed)
+ import zlib
+ compressed = zlib.compress(processed)
+ data = ToCArray(compressed)
+ c_out.write(HEADER_TEMPLATE % {
+ 'sources_type': 'unsigned char',
+ 'sources_data': data,
+ 'sources_name': 'compressedSources',
+ 'compressed_total_length': len(compressed),
+ 'raw_total_length': len(processed),
+ 'namespace': namespace
+ })
+
+def preprocess(cxx, preprocessorOption, source, args = []):
+ if (not os.path.exists(cxx[0])):
+ cxx[0] = which.which(cxx[0])
+ # Clang seems to complain and not output anything if the extension of the
+ # input is not something it recognizes, so just fake a .cpp here.
+ tmpIn = 'self-hosting-cpp-input.cpp';
+ tmpOut = 'self-hosting-preprocessed.pp';
+ outputArg = shlex.split(preprocessorOption + tmpOut)
+
+ with open(tmpIn, 'wb') as input:
+ input.write(source)
+ print(' '.join(cxx + outputArg + args + [tmpIn]))
+ result = subprocess.Popen(cxx + outputArg + args + [tmpIn]).wait()
+ if (result != 0):
+ sys.exit(result);
+ with open(tmpOut, 'r') as output:
+ processed = output.read();
+ os.remove(tmpIn)
+ os.remove(tmpOut)
+ return processed
+
+def messages(jsmsg):
+ defines = []
+ for line in open(jsmsg):
+ match = re.match("MSG_DEF\((JSMSG_(\w+))", line)
+ if match:
+ defines.append("#define %s %i" % (match.group(1), len(defines)))
+ else:
+ # Make sure that MSG_DEF isn't preceded by whitespace
+ assert not line.strip().startswith("MSG_DEF")
+ return '\n'.join(defines)
+
+def get_config_defines(buildconfig):
+ # Collect defines equivalent to ACDEFINES and add MOZ_DEBUG_DEFINES.
+ env = {key: value for key, value in buildconfig.defines.iteritems()
+ if key not in buildconfig.non_global_defines}
+ for define in buildconfig.substs['MOZ_DEBUG_DEFINES']:
+ env[define] = 1
+ return env
+
+def process_inputs(namespace, c_out, msg_file, inputs):
+ deps = [path for path in inputs if path.endswith(".h") or path.endswith(".h.js")]
+ sources = [path for path in inputs if path.endswith(".js") and not path.endswith(".h.js")]
+ assert len(deps) + len(sources) == len(inputs)
+ cxx = shlex.split(buildconfig.substs['CXX'])
+ cxx_option = buildconfig.substs['PREPROCESS_OPTION']
+ env = get_config_defines(buildconfig)
+ js_path = re.sub(r"\.out\.h$", "", c_out.name) + ".js"
+ msgs = messages(msg_file)
+ with open(js_path, 'w') as js_out:
+ embed(cxx, cxx_option, msgs, sources, c_out, js_out, namespace, env)
+
+def generate_selfhosted(c_out, msg_file, *inputs):
+ # Called from moz.build to embed selfhosted JS.
+ process_inputs('selfhosted', c_out, msg_file, inputs)
+
+def generate_shellmoduleloader(c_out, msg_file, *inputs):
+ # Called from moz.build to embed shell module loader JS.
+ process_inputs('moduleloader', c_out, msg_file, inputs)
diff --git a/js/src/builtin/make_intl_data.py b/js/src/builtin/make_intl_data.py
new file mode 100755
index 000000000..b81d5951f
--- /dev/null
+++ b/js/src/builtin/make_intl_data.py
@@ -0,0 +1,992 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# 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/.
+
+""" Usage:
+ make_intl_data.py langtags [language-subtag-registry.txt]
+ make_intl_data.py tzdata
+
+ Target "langtags":
+ This script extracts information about mappings between deprecated and
+ current BCP 47 language tags from the IANA Language Subtag Registry and
+ converts it to JavaScript object definitions in IntlData.js. The definitions
+ are used in Intl.js.
+
+ The IANA Language Subtag Registry is imported from
+ https://www.iana.org/assignments/language-subtag-registry
+ and uses the syntax specified in
+ https://tools.ietf.org/html/rfc5646#section-3
+
+
+ Target "tzdata":
+ This script computes which time zone informations are not up-to-date in ICU
+ and provides the necessary mappings to workaround this problem.
+ https://ssl.icu-project.org/trac/ticket/12044
+"""
+
+from __future__ import print_function
+import os
+import re
+import io
+import codecs
+import sys
+import tarfile
+import tempfile
+import urllib2
+import urlparse
+from contextlib import closing
+from functools import partial
+from itertools import chain, ifilter, ifilterfalse, imap, tee
+from operator import attrgetter, itemgetter
+
+def readRegistryRecord(registry):
+ """ Yields the records of the IANA Language Subtag Registry as dictionaries. """
+ record = {}
+ for line in registry:
+ line = line.strip()
+ if line == "":
+ continue
+ if line == "%%":
+ yield record
+ record = {}
+ else:
+ if ":" in line:
+ key, value = line.split(":", 1)
+ key, value = key.strip(), value.strip()
+ record[key] = value
+ else:
+ # continuation line
+ record[key] += " " + line
+ if record:
+ yield record
+ return
+
+
+def readRegistry(registry):
+ """ Reads IANA Language Subtag Registry and extracts information for Intl.js.
+
+ Information extracted:
+ - langTagMappings: mappings from complete language tags to preferred
+ complete language tags
+ - langSubtagMappings: mappings from subtags to preferred subtags
+ - extlangMappings: mappings from extlang subtags to preferred subtags,
+ with prefix to be removed
+ Returns these three mappings as dictionaries, along with the registry's
+ file date.
+
+ We also check that mappings for language subtags don't affect extlang
+ subtags and vice versa, so that CanonicalizeLanguageTag doesn't have
+ to separate them for processing. Region codes are separated by case,
+ and script codes by length, so they're unproblematic.
+ """
+ langTagMappings = {}
+ langSubtagMappings = {}
+ extlangMappings = {}
+ languageSubtags = set()
+ extlangSubtags = set()
+
+ for record in readRegistryRecord(registry):
+ if "File-Date" in record:
+ fileDate = record["File-Date"]
+ continue
+
+ if record["Type"] == "grandfathered":
+ # Grandfathered tags don't use standard syntax, so
+ # CanonicalizeLanguageTag expects the mapping table to provide
+ # the final form for all.
+ # For langTagMappings, keys must be in lower case; values in
+ # the case used in the registry.
+ tag = record["Tag"]
+ if "Preferred-Value" in record:
+ langTagMappings[tag.lower()] = record["Preferred-Value"]
+ else:
+ langTagMappings[tag.lower()] = tag
+ elif record["Type"] == "redundant":
+ # For langTagMappings, keys must be in lower case; values in
+ # the case used in the registry.
+ if "Preferred-Value" in record:
+ langTagMappings[record["Tag"].lower()] = record["Preferred-Value"]
+ elif record["Type"] in ("language", "script", "region", "variant"):
+ # For langSubtagMappings, keys and values must be in the case used
+ # in the registry.
+ subtag = record["Subtag"]
+ if record["Type"] == "language":
+ languageSubtags.add(subtag)
+ if "Preferred-Value" in record:
+ if subtag == "heploc":
+ # The entry for heploc is unique in its complexity; handle
+ # it as special case below.
+ continue
+ if "Prefix" in record:
+ # This might indicate another heploc-like complex case.
+ raise Exception("Please evaluate: subtag mapping with prefix value.")
+ langSubtagMappings[subtag] = record["Preferred-Value"]
+ elif record["Type"] == "extlang":
+ # For extlangMappings, keys must be in the case used in the
+ # registry; values are records with the preferred value and the
+ # prefix to be removed.
+ subtag = record["Subtag"]
+ extlangSubtags.add(subtag)
+ if "Preferred-Value" in record:
+ preferred = record["Preferred-Value"]
+ prefix = record["Prefix"]
+ extlangMappings[subtag] = {"preferred": preferred, "prefix": prefix}
+ else:
+ # No other types are allowed by
+ # https://tools.ietf.org/html/rfc5646#section-3.1.3
+ assert False, "Unrecognized Type: {0}".format(record["Type"])
+
+ # Check that mappings for language subtags and extlang subtags don't affect
+ # each other.
+ for lang in languageSubtags:
+ if lang in extlangMappings and extlangMappings[lang]["preferred"] != lang:
+ raise Exception("Conflict: lang with extlang mapping: " + lang)
+ for extlang in extlangSubtags:
+ if extlang in langSubtagMappings:
+ raise Exception("Conflict: extlang with lang mapping: " + extlang)
+
+ # Special case for heploc.
+ langTagMappings["ja-latn-hepburn-heploc"] = "ja-Latn-alalc97"
+
+ return {"fileDate": fileDate,
+ "langTagMappings": langTagMappings,
+ "langSubtagMappings": langSubtagMappings,
+ "extlangMappings": extlangMappings}
+
+
+def writeMappingsVar(intlData, dict, name, description, fileDate, url):
+ """ Writes a variable definition with a mapping table to file intlData.
+
+ Writes the contents of dictionary dict to file intlData with the given
+ variable name and a comment with description, fileDate, and URL.
+ """
+ intlData.write("\n")
+ intlData.write("// {0}.\n".format(description))
+ intlData.write("// Derived from IANA Language Subtag Registry, file date {0}.\n".format(fileDate))
+ intlData.write("// {0}\n".format(url))
+ intlData.write("var {0} = {{\n".format(name))
+ keys = sorted(dict)
+ for key in keys:
+ if isinstance(dict[key], basestring):
+ value = '"{0}"'.format(dict[key])
+ else:
+ preferred = dict[key]["preferred"]
+ prefix = dict[key]["prefix"]
+ value = '{{preferred: "{0}", prefix: "{1}"}}'.format(preferred, prefix)
+ intlData.write(' "{0}": {1},\n'.format(key, value))
+ intlData.write("};\n")
+
+
+def writeLanguageTagData(intlData, fileDate, url, langTagMappings, langSubtagMappings, extlangMappings):
+ """ Writes the language tag data to the Intl data file. """
+ writeMappingsVar(intlData, langTagMappings, "langTagMappings",
+ "Mappings from complete tags to preferred values", fileDate, url)
+ writeMappingsVar(intlData, langSubtagMappings, "langSubtagMappings",
+ "Mappings from non-extlang subtags to preferred values", fileDate, url)
+ writeMappingsVar(intlData, extlangMappings, "extlangMappings",
+ "Mappings from extlang subtags to preferred values", fileDate, url)
+
+def updateLangTags(args):
+ """ Update the IntlData.js file. """
+ url = args.url
+ out = args.out
+ filename = args.file
+
+ print("Arguments:")
+ print("\tDownload url: %s" % url)
+ print("\tLocal registry: %s" % filename)
+ print("\tOutput file: %s" % out)
+ print("")
+
+ if filename is not None:
+ print("Always make sure you have the newest language-subtag-registry.txt!")
+ registry = codecs.open(filename, "r", encoding="utf-8")
+ else:
+ print("Downloading IANA Language Subtag Registry...")
+ with closing(urllib2.urlopen(url)) as reader:
+ text = reader.read().decode("utf-8")
+ registry = codecs.open("language-subtag-registry.txt", "w+", encoding="utf-8")
+ registry.write(text)
+ registry.seek(0)
+
+ print("Processing IANA Language Subtag Registry...")
+ with closing(registry) as reg:
+ data = readRegistry(reg)
+ fileDate = data["fileDate"]
+ langTagMappings = data["langTagMappings"]
+ langSubtagMappings = data["langSubtagMappings"]
+ extlangMappings = data["extlangMappings"]
+
+ print("Writing Intl data...")
+ with codecs.open(out, "w", encoding="utf-8") as intlData:
+ intlData.write("// Generated by make_intl_data.py. DO NOT EDIT.\n")
+ writeLanguageTagData(intlData, fileDate, url, langTagMappings, langSubtagMappings, extlangMappings)
+
+def flines(filepath, encoding="utf-8"):
+ """ Open filepath and iterate over its content. """
+ with io.open(filepath, mode="r", encoding=encoding) as f:
+ for line in f:
+ yield line
+
+class Zone:
+ """ Time zone with optional file name. """
+
+ def __init__(self, name, filename=""):
+ self.name = name
+ self.filename = filename
+ def __eq__(self, other):
+ return hasattr(other, "name") and self.name == other.name
+ def __cmp__(self, other):
+ if self.name == other.name:
+ return 0
+ if self.name < other.name:
+ return -1
+ return 1
+ def __hash__(self):
+ return hash(self.name)
+ def __str__(self):
+ return self.name
+ def __repr__(self):
+ return self.name
+
+class TzDataDir:
+ """ tzdata source from a directory. """
+
+ def __init__(self, obj):
+ self.name = partial(os.path.basename, obj)
+ self.resolve = partial(os.path.join, obj)
+ self.basename = os.path.basename
+ self.isfile = os.path.isfile
+ self.listdir = partial(os.listdir, obj)
+ self.readlines = flines
+
+class TzDataFile:
+ """ tzdata source from a file (tar or gzipped). """
+
+ def __init__(self, obj):
+ self.name = lambda: os.path.splitext(os.path.splitext(os.path.basename(obj))[0])[0]
+ self.resolve = obj.getmember
+ self.basename = attrgetter("name")
+ self.isfile = tarfile.TarInfo.isfile
+ self.listdir = obj.getnames
+ self.readlines = partial(self._tarlines, obj)
+
+ def _tarlines(self, tar, m):
+ with closing(tar.extractfile(m)) as f:
+ for line in codecs.EncodedFile(f, "utf-8"):
+ yield line
+
+def validateTimeZones(zones, links):
+ """ Validate the zone and link entries. """
+ linkZones = set(links.viewkeys())
+ intersect = linkZones.intersection(zones)
+ if intersect:
+ raise RuntimeError("Links also present in zones: %s" % intersect)
+
+ zoneNames = set(z.name for z in zones)
+ linkTargets = set(links.viewvalues())
+ if not linkTargets.issubset(zoneNames):
+ raise RuntimeError("Link targets not found: %s" % linkTargets.difference(zoneNames))
+
+def partition(iterable, *predicates):
+ def innerPartition(pred, it):
+ it1, it2 = tee(it)
+ return (ifilter(pred, it1), ifilterfalse(pred, it2))
+ if len(predicates) == 0:
+ return iterable
+ (left, right) = innerPartition(predicates[0], iterable)
+ if len(predicates) == 1:
+ return (left, right)
+ return tuple([left] + list(partition(right, *predicates[1:])))
+
+def listIANAFiles(tzdataDir):
+ def isTzFile(d, m, f):
+ return m(f) and d.isfile(d.resolve(f))
+ return ifilter(partial(isTzFile, tzdataDir, re.compile("^[a-z0-9]+$").match), tzdataDir.listdir())
+
+def readIANAFiles(tzdataDir, files):
+ """ Read all IANA time zone files from the given iterable. """
+ nameSyntax = "[\w/+\-]+"
+ pZone = re.compile(r"Zone\s+(?P<name>%s)\s+.*" % nameSyntax)
+ pLink = re.compile(r"Link\s+(?P<target>%s)\s+(?P<name>%s)(?:\s+#.*)?" % (nameSyntax, nameSyntax))
+
+ def createZone(line, fname):
+ match = pZone.match(line)
+ name = match.group("name")
+ return Zone(name, fname)
+
+ def createLink(line, fname):
+ match = pLink.match(line)
+ (name, target) = match.group("name", "target")
+ return (Zone(name, fname), target)
+
+ zones = set()
+ links = dict()
+ for filename in files:
+ filepath = tzdataDir.resolve(filename)
+ for line in tzdataDir.readlines(filepath):
+ if line.startswith("Zone"):
+ zones.add(createZone(line, filename))
+ if line.startswith("Link"):
+ (link, target) = createLink(line, filename)
+ links[link] = target
+
+ return (zones, links)
+
+def readIANATimeZones(tzdataDir, ignoreBackzone, ignoreFactory):
+ """ Read the IANA time zone information from `tzdataDir`. """
+
+ backzoneFiles = {"backzone"}
+ (bkfiles, tzfiles) = partition(listIANAFiles(tzdataDir), backzoneFiles.__contains__)
+
+ # Read zone and link infos.
+ (zones, links) = readIANAFiles(tzdataDir, tzfiles)
+ (backzones, backlinks) = readIANAFiles(tzdataDir, bkfiles)
+
+ # Remove the placeholder time zone "Factory".
+ if ignoreFactory:
+ zones.remove(Zone("Factory"))
+
+ # Merge with backzone data.
+ if not ignoreBackzone:
+ zones |= backzones
+ links = {name: target for name, target in links.iteritems() if name not in backzones}
+ links.update(backlinks)
+
+ validateTimeZones(zones, links)
+
+ return (zones, links)
+
+def readICUResourceFile(filename):
+ """ Read an ICU resource file.
+
+ Yields (<table-name>, <startOrEnd>, <value>) for each table.
+ """
+
+ numberValue = r"-?\d+"
+ stringValue = r'".+?"'
+ asVector = lambda val: r"%s(?:\s*,\s*%s)*" % (val, val)
+ numberVector = asVector(numberValue)
+ stringVector = asVector(stringValue)
+
+ reNumberVector = re.compile(numberVector)
+ reStringVector = re.compile(stringVector)
+ reNumberValue = re.compile(numberValue)
+ reStringValue = re.compile(stringValue)
+ def parseValue(value):
+ m = reNumberVector.match(value)
+ if m:
+ return [int(v) for v in reNumberValue.findall(value)]
+ m = reStringVector.match(value)
+ if m:
+ return [v[1:-1] for v in reStringValue.findall(value)]
+ raise RuntimeError("unknown value type: %s" % value)
+
+ def extractValue(values):
+ if len(values) == 0:
+ return None
+ if len(values) == 1:
+ return values[0]
+ return values
+
+ def line(*args):
+ maybeMultiComments = r"(?:/\*[^*]*\*/)*"
+ maybeSingleComment = r"(?://.*)?"
+ lineStart = "^%s" % maybeMultiComments
+ lineEnd = "%s\s*%s$" % (maybeMultiComments, maybeSingleComment)
+ return re.compile(r"\s*".join(chain([lineStart], args, [lineEnd])))
+
+ tableName = r'(?P<quote>"?)(?P<name>.+?)(?P=quote)'
+ tableValue = r"(?P<value>%s|%s)" % (numberVector, stringVector)
+
+ reStartTable = line(tableName, r"\{")
+ reEndTable = line(r"\}")
+ reSingleValue = line(r",?", tableValue, r",?")
+ reCompactTable = line(tableName, r"\{", tableValue, r"\}")
+ reEmptyLine = line()
+
+ tables = []
+ currentTable = lambda: "|".join(tables)
+ values = []
+ for line in flines(filename, "utf-8-sig"):
+ line = line.strip()
+ if line == "":
+ continue
+
+ m = reEmptyLine.match(line)
+ if m:
+ continue
+
+ m = reStartTable.match(line)
+ if m:
+ assert len(values) == 0
+ tables.append(m.group("name"))
+ continue
+
+ m = reEndTable.match(line)
+ if m:
+ yield (currentTable(), extractValue(values))
+ tables.pop()
+ values = []
+ continue
+
+ m = reCompactTable.match(line)
+ if m:
+ assert len(values) == 0
+ tables.append(m.group("name"))
+ yield (currentTable(), extractValue(parseValue(m.group("value"))))
+ tables.pop()
+ continue
+
+ m = reSingleValue.match(line)
+ if m and tables:
+ values.extend(parseValue(m.group("value")))
+ continue
+
+ raise RuntimeError("unknown entry: %s" % line)
+
+def readICUTimeZonesFromTimezoneTypes(icuTzDir):
+ """ Read the ICU time zone information from `icuTzDir`/timezoneTypes.txt
+ and returns the tuple (zones, links).
+ """
+ typeMapTimeZoneKey = "timezoneTypes:table(nofallback)|typeMap|timezone|"
+ typeAliasTimeZoneKey = "timezoneTypes:table(nofallback)|typeAlias|timezone|"
+ toTimeZone = lambda name: Zone(name.replace(":", "/"))
+
+ zones = set()
+ links = dict()
+
+ for name, value in readICUResourceFile(os.path.join(icuTzDir, "timezoneTypes.txt")):
+ if name.startswith(typeMapTimeZoneKey):
+ zones.add(toTimeZone(name[len(typeMapTimeZoneKey):]))
+ if name.startswith(typeAliasTimeZoneKey):
+ links[toTimeZone(name[len(typeAliasTimeZoneKey):])] = value
+
+ # Remove the ICU placeholder time zone "Etc/Unknown".
+ zones.remove(Zone("Etc/Unknown"))
+
+ # tzdata2017c removed the link Canada/East-Saskatchewan -> America/Regina,
+ # but it is still present in ICU sources. Manually remove it to keep our
+ # tables consistent with IANA.
+ del links[Zone("Canada/East-Saskatchewan")]
+
+ validateTimeZones(zones, links)
+
+ return (zones, links)
+
+def readICUTimeZonesFromZoneInfo(icuTzDir, ignoreFactory):
+ """ Read the ICU time zone information from `icuTzDir`/zoneinfo64.txt
+ and returns the tuple (zones, links).
+ """
+ zoneKey = "zoneinfo64:table(nofallback)|Zones:array|:table"
+ linkKey = "zoneinfo64:table(nofallback)|Zones:array|:int"
+ namesKey = "zoneinfo64:table(nofallback)|Names"
+
+ tzId = 0
+ tzLinks = dict()
+ tzNames = []
+
+ for name, value in readICUResourceFile(os.path.join(icuTzDir, "zoneinfo64.txt")):
+ if name == zoneKey:
+ tzId += 1
+ elif name == linkKey:
+ tzLinks[tzId] = int(value)
+ tzId += 1
+ elif name == namesKey:
+ tzNames.extend(value)
+
+ links = dict((Zone(tzNames[zone]), tzNames[target]) for (zone, target) in tzLinks.iteritems())
+ zones = set([Zone(v) for v in tzNames if Zone(v) not in links])
+
+ # Remove the ICU placeholder time zone "Etc/Unknown".
+ zones.remove(Zone("Etc/Unknown"))
+
+ # tzdata2017c removed the link Canada/East-Saskatchewan -> America/Regina,
+ # but it is still present in ICU sources. Manually remove it to keep our
+ # tables consistent with IANA.
+ del links[Zone("Canada/East-Saskatchewan")]
+
+ # Remove the placeholder time zone "Factory".
+ if ignoreFactory:
+ zones.remove(Zone("Factory"))
+
+ validateTimeZones(zones, links)
+
+ return (zones, links)
+
+def readICUTimeZones(icuDir, icuTzDir, ignoreFactory):
+ # zoneinfo64.txt contains the supported time zones by ICU. This data is
+ # generated from tzdata files, it doesn't include "backzone" in stock ICU.
+ (zoneinfoZones, zoneinfoLinks) = readICUTimeZonesFromZoneInfo(icuTzDir, ignoreFactory)
+
+ # timezoneTypes.txt contains the canonicalization information for ICU. This
+ # data is generated from CLDR files. It includes data about time zones from
+ # tzdata's "backzone" file.
+ (typesZones, typesLinks) = readICUTimeZonesFromTimezoneTypes(icuTzDir)
+
+ # Information in zoneinfo64 should be a superset of timezoneTypes.
+ inZoneInfo64 = lambda zone: zone in zoneinfoZones or zone in zoneinfoLinks
+
+ # Remove legacy ICU time zones from zoneinfo64 data.
+ (legacyZones, legacyLinks) = readICULegacyZones(icuDir)
+ zoneinfoZones = set(zone for zone in zoneinfoZones if zone not in legacyZones)
+ zoneinfoLinks = dict((zone, target) for (zone, target) in zoneinfoLinks.iteritems() if zone not in legacyLinks)
+
+ notFoundInZoneInfo64 = [zone for zone in typesZones if not inZoneInfo64(zone)]
+ if notFoundInZoneInfo64:
+ raise RuntimeError("Missing time zones in zoneinfo64.txt: %s" % notFoundInZoneInfo64)
+
+ notFoundInZoneInfo64 = [zone for zone in typesLinks.iterkeys() if not inZoneInfo64(zone)]
+ if notFoundInZoneInfo64:
+ raise RuntimeError("Missing time zones in zoneinfo64.txt: %s" % notFoundInZoneInfo64)
+
+ # zoneinfo64.txt only defines the supported time zones by ICU, the canonicalization
+ # rules are defined through timezoneTypes.txt. Merge both to get the actual zones
+ # and links used by ICU.
+ icuZones = set(chain(
+ (zone for zone in zoneinfoZones if zone not in typesLinks),
+ (zone for zone in typesZones)
+ ))
+ icuLinks = dict(chain(
+ ((zone, target) for (zone, target) in zoneinfoLinks.iteritems() if zone not in typesZones),
+ ((zone, target) for (zone, target) in typesLinks.iteritems())
+ ))
+
+ return (icuZones, icuLinks)
+
+
+def readICULegacyZones(icuDir):
+ """ Read the ICU legacy time zones from `icuTzDir`/tools/tzcode/icuzones
+ and returns the tuple (zones, links).
+ """
+ tzdir = TzDataDir(os.path.join(icuDir, "tools/tzcode"))
+ (zones, links) = readIANAFiles(tzdir, ["icuzones"])
+
+ # Remove the ICU placeholder time zone "Etc/Unknown".
+ zones.remove(Zone("Etc/Unknown"))
+
+ # tzdata2017c removed the link Canada/East-Saskatchewan -> America/Regina,
+ # but it is still present in ICU sources. Manually tag it as a legacy time
+ # zone so our tables are kept consistent with IANA.
+ links[Zone("Canada/East-Saskatchewan")] = "America/Regina"
+
+ return (zones, links)
+
+def icuTzDataVersion(icuTzDir):
+ """ Read the ICU time zone version from `icuTzDir`/zoneinfo64.txt. """
+ def searchInFile(pattern, f):
+ p = re.compile(pattern)
+ for line in flines(f, "utf-8-sig"):
+ m = p.search(line)
+ if m:
+ return m.group(1)
+ return None
+
+ zoneinfo = os.path.join(icuTzDir, "zoneinfo64.txt")
+ if not os.path.isfile(zoneinfo):
+ raise RuntimeError("file not found: %s" % zoneinfo)
+ version = searchInFile("^//\s+tz version:\s+([0-9]{4}[a-z])$", zoneinfo)
+ if version is None:
+ raise RuntimeError("%s does not contain a valid tzdata version string" % zoneinfo)
+ return version
+
+def findIncorrectICUZones(ianaZones, ianaLinks, icuZones, icuLinks, ignoreBackzone):
+ """ Find incorrect ICU zone entries. """
+ isIANATimeZone = lambda zone: zone in ianaZones or zone in ianaLinks
+ isICUTimeZone = lambda zone: zone in icuZones or zone in icuLinks
+ isICULink = lambda zone: zone in icuLinks
+
+ # All IANA zones should be present in ICU.
+ missingTimeZones = [zone for zone in ianaZones if not isICUTimeZone(zone)]
+ # Normally zones in backzone are also present as links in one of the other
+ # time zone files. The only exception to this rule is the Asia/Hanoi time
+ # zone, this zone is only present in the backzone file.
+ expectedMissing = [] if ignoreBackzone else [Zone("Asia/Hanoi")]
+ if missingTimeZones != expectedMissing:
+ raise RuntimeError("Not all zones are present in ICU, did you forget "
+ "to run intl/update-tzdata.sh? %s" % missingTimeZones)
+
+ # Zones which are only present in ICU?
+ additionalTimeZones = [zone for zone in icuZones if not isIANATimeZone(zone)]
+ if additionalTimeZones:
+ raise RuntimeError("Additional zones present in ICU, did you forget "
+ "to run intl/update-tzdata.sh? %s" % additionalTimeZones)
+
+ # Zones which are marked as links in ICU.
+ result = ((zone, icuLinks[zone]) for zone in ianaZones if isICULink(zone))
+
+ # Remove unnecessary UTC mappings.
+ utcnames = ["Etc/UTC", "Etc/UCT", "Etc/GMT"]
+ result = ifilterfalse(lambda (zone, target): zone.name in utcnames, result)
+
+ return sorted(result, key=itemgetter(0))
+
+def findIncorrectICULinks(ianaZones, ianaLinks, icuZones, icuLinks):
+ """ Find incorrect ICU link entries. """
+ isIANATimeZone = lambda zone: zone in ianaZones or zone in ianaLinks
+ isICUTimeZone = lambda zone: zone in icuZones or zone in icuLinks
+ isICULink = lambda zone: zone in icuLinks
+ isICUZone = lambda zone: zone in icuZones
+
+ # All links should be present in ICU.
+ missingTimeZones = [zone for zone in ianaLinks.iterkeys() if not isICUTimeZone(zone)]
+ if missingTimeZones:
+ raise RuntimeError("Not all zones are present in ICU, did you forget "
+ "to run intl/update-tzdata.sh? %s" % missingTimeZones)
+
+ # Links which are only present in ICU?
+ additionalTimeZones = [zone for zone in icuLinks.iterkeys() if not isIANATimeZone(zone)]
+ if additionalTimeZones:
+ raise RuntimeError("Additional links present in ICU, did you forget "
+ "to run intl/update-tzdata.sh? %s" % additionalTimeZones)
+
+ result = chain(
+ # IANA links which have a different target in ICU.
+ ((zone, target, icuLinks[zone]) for (zone, target) in ianaLinks.iteritems() if isICULink(zone) and target != icuLinks[zone]),
+
+ # IANA links which are zones in ICU.
+ ((zone, target, zone.name) for (zone, target) in ianaLinks.iteritems() if isICUZone(zone))
+ )
+
+ # Remove unnecessary UTC mappings.
+ utcnames = ["Etc/UTC", "Etc/UCT", "Etc/GMT"]
+ result = ifilterfalse(lambda (zone, target, icuTarget): target in utcnames and icuTarget in utcnames, result)
+
+ return sorted(result, key=itemgetter(0))
+
+generatedFileWarning = u"// Generated by make_intl_data.py. DO NOT EDIT."
+tzdataVersionComment = u"// tzdata version = {0}"
+
+def processTimeZones(tzdataDir, icuDir, icuTzDir, version, ignoreBackzone, ignoreFactory, out):
+ """ Read the time zone info and create a new time zone cpp file. """
+ print("Processing tzdata mapping...")
+ (ianaZones, ianaLinks) = readIANATimeZones(tzdataDir, ignoreBackzone, ignoreFactory)
+ (icuZones, icuLinks) = readICUTimeZones(icuDir, icuTzDir, ignoreFactory)
+ (legacyZones, legacyLinks) = readICULegacyZones(icuDir)
+
+ incorrectZones = findIncorrectICUZones(ianaZones, ianaLinks, icuZones, icuLinks, ignoreBackzone)
+ if not incorrectZones:
+ print("<<< No incorrect ICU time zones found, please update Intl.js! >>>")
+ print("<<< Maybe https://ssl.icu-project.org/trac/ticket/12044 was fixed? >>>")
+
+ incorrectLinks = findIncorrectICULinks(ianaZones, ianaLinks, icuZones, icuLinks)
+ if not incorrectLinks:
+ print("<<< No incorrect ICU time zone links found, please update Intl.js! >>>")
+ print("<<< Maybe https://ssl.icu-project.org/trac/ticket/12044 was fixed? >>>")
+
+ print("Writing Intl tzdata file...")
+ with io.open(out, mode="w", encoding="utf-8", newline="") as f:
+ println = partial(print, file=f)
+
+ println(generatedFileWarning)
+ println(tzdataVersionComment.format(version))
+ println(u"")
+
+ println(u"#ifndef builtin_IntlTimeZoneData_h")
+ println(u"#define builtin_IntlTimeZoneData_h")
+ println(u"")
+
+ println(u"namespace js {")
+ println(u"namespace timezone {")
+ println(u"")
+
+ println(u"// Format:")
+ println(u'// "ZoneName" // ICU-Name [time zone file]')
+ println(u"const char* const ianaZonesTreatedAsLinksByICU[] = {")
+ for (zone, icuZone) in incorrectZones:
+ println(u' "%s", // %s [%s]' % (zone, icuZone, zone.filename))
+ println(u"};")
+ println(u"")
+
+ println(u"// Format:")
+ println(u'// "LinkName", "Target" // ICU-Target [time zone file]')
+ println(u"struct LinkAndTarget");
+ println(u"{");
+ println(u" const char* const link;");
+ println(u" const char* const target;");
+ println(u"};");
+ println(u"")
+ println(u"const LinkAndTarget ianaLinksCanonicalizedDifferentlyByICU[] = {")
+ for (zone, target, icuTarget) in incorrectLinks:
+ println(u' { "%s", "%s" }, // %s [%s]' % (zone, target, icuTarget, zone.filename))
+ println(u"};")
+ println(u"")
+
+ println(u"// Legacy ICU time zones, these are not valid IANA time zone names. We also")
+ println(u"// disallow the old and deprecated System V time zones.")
+ println(u"// https://ssl.icu-project.org/repos/icu/trunk/icu4c/source/tools/tzcode/icuzones")
+ println(u"const char* const legacyICUTimeZones[] = {")
+ for zone in chain(sorted(legacyLinks.keys()), sorted(legacyZones)):
+ println(u' "%s",' % zone)
+ println(u"};")
+ println(u"")
+
+ println(u"} // namespace timezone")
+ println(u"} // namespace js")
+ println(u"")
+ println(u"#endif /* builtin_IntlTimeZoneData_h */")
+
+def updateBackzoneLinks(tzdataDir, links):
+ (backzoneZones, backzoneLinks) = readIANAFiles(tzdataDir, ["backzone"])
+ (stableZones, updatedLinks, updatedZones) = partition(
+ links.iteritems(),
+ # Link not changed in backzone.
+ lambda (zone, target): zone not in backzoneLinks and zone not in backzoneZones,
+ # Link has a new target.
+ lambda (zone, target): zone in backzoneLinks,
+ )
+ # Keep stable zones and links with updated target.
+ return dict(chain(
+ stableZones,
+ imap(lambda (zone, target): (zone, backzoneLinks[zone]), updatedLinks)
+ ))
+
+def generateTzDataLinkTestContent(testDir, version, fileName, description, links):
+ with io.open(os.path.join(testDir, fileName), mode="w", encoding="utf-8", newline="") as f:
+ println = partial(print, file=f)
+
+ println(u'// |reftest| skip-if(!this.hasOwnProperty("Intl"))')
+ println(u"")
+ println(generatedFileWarning)
+ println(tzdataVersionComment.format(version))
+ println(u"""
+const tzMapper = [
+ x => x,
+ x => x.toUpperCase(),
+ x => x.toLowerCase(),
+];
+""")
+
+ println(description)
+ println(u"const links = {")
+ for (zone, target) in sorted(links, key=itemgetter(0)):
+ println(u' "%s": "%s",' % (zone, target))
+ println(u"};")
+
+ println(u"""
+for (let [linkName, target] of Object.entries(links)) {
+ if (target === "Etc/UTC" || target === "Etc/GMT")
+ target = "UTC";
+
+ for (let map of tzMapper) {
+ let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(linkName)});
+ let resolvedTimeZone = dtf.resolvedOptions().timeZone;
+ assertEq(resolvedTimeZone, target, `${linkName} -> ${target}`);
+ }
+}
+""")
+ println(u"""
+if (typeof reportCompare === "function")
+ reportCompare(0, 0, "ok");
+""")
+
+def generateTzDataTestBackwardLinks(tzdataDir, version, ignoreBackzone, testDir):
+ (zones, links) = readIANAFiles(tzdataDir, ["backward"])
+ assert len(zones) == 0
+
+ if not ignoreBackzone:
+ links = updateBackzoneLinks(tzdataDir, links)
+
+ generateTzDataLinkTestContent(
+ testDir, version,
+ "timeZone_backward_links.js",
+ u"// Link names derived from IANA Time Zone Database, backward file.",
+ links.iteritems()
+ )
+
+def generateTzDataTestNotBackwardLinks(tzdataDir, version, ignoreBackzone, testDir):
+ tzfiles = ifilterfalse({"backward", "backzone"}.__contains__, listIANAFiles(tzdataDir))
+ (zones, links) = readIANAFiles(tzdataDir, tzfiles)
+
+ if not ignoreBackzone:
+ links = updateBackzoneLinks(tzdataDir, links)
+
+ generateTzDataLinkTestContent(
+ testDir, version,
+ "timeZone_notbackward_links.js",
+ u"// Link names derived from IANA Time Zone Database, excluding backward file.",
+ links.iteritems()
+ )
+
+def generateTzDataTestBackzone(tzdataDir, version, ignoreBackzone, testDir):
+ backzoneFiles = {"backzone"}
+ (bkfiles, tzfiles) = partition(listIANAFiles(tzdataDir), backzoneFiles.__contains__)
+
+ # Read zone and link infos.
+ (zones, links) = readIANAFiles(tzdataDir, tzfiles)
+ (backzones, backlinks) = readIANAFiles(tzdataDir, bkfiles)
+
+ if not ignoreBackzone:
+ comment=u"""\
+// This file was generated with historical, pre-1970 backzone information
+// respected. Therefore, every zone key listed below is its own Zone, not
+// a Link to a modern-day target as IANA ignoring backzones would say.
+
+"""
+ else:
+ comment=u"""\
+// This file was generated while ignoring historical, pre-1970 backzone
+// information. Therefore, every zone key listed below is part of a Link
+// whose target is the corresponding value.
+
+"""
+
+ generateTzDataLinkTestContent(
+ testDir, version,
+ "timeZone_backzone.js",
+ comment + u"// Backzone zones derived from IANA Time Zone Database.",
+ ((zone, zone if not ignoreBackzone else links[zone]) for zone in backzones if zone in links)
+ )
+
+def generateTzDataTestBackzoneLinks(tzdataDir, version, ignoreBackzone, testDir):
+ backzoneFiles = {"backzone"}
+ (bkfiles, tzfiles) = partition(listIANAFiles(tzdataDir), backzoneFiles.__contains__)
+
+ # Read zone and link infos.
+ (zones, links) = readIANAFiles(tzdataDir, tzfiles)
+ (backzones, backlinks) = readIANAFiles(tzdataDir, bkfiles)
+
+ if not ignoreBackzone:
+ comment=u"""\
+// This file was generated with historical, pre-1970 backzone information
+// respected. Therefore, every zone key listed below points to a target
+// in the backzone file and not to its modern-day target as IANA ignoring
+// backzones would say.
+
+"""
+ else:
+ comment=u"""\
+// This file was generated while ignoring historical, pre-1970 backzone
+// information. Therefore, every zone key listed below is part of a Link
+// whose target is the corresponding value ignoring any backzone entries.
+
+"""
+
+ generateTzDataLinkTestContent(
+ testDir, version,
+ "timeZone_backzone_links.js",
+ comment + u"// Backzone links derived from IANA Time Zone Database.",
+ ((zone, target if not ignoreBackzone else links[zone]) for (zone, target) in backlinks.iteritems())
+ )
+
+def generateTzDataTests(tzdataDir, version, ignoreBackzone, testDir):
+ generateTzDataTestBackwardLinks(tzdataDir, version, ignoreBackzone, testDir)
+ generateTzDataTestNotBackwardLinks(tzdataDir, version, ignoreBackzone, testDir)
+ generateTzDataTestBackzone(tzdataDir, version, ignoreBackzone, testDir)
+ generateTzDataTestBackzoneLinks(tzdataDir, version, ignoreBackzone, testDir)
+
+def updateTzdata(args):
+ """ Update the time zone cpp file. """
+
+ # This script must reside in js/src/builtin to work correctly.
+ (thisDir, thisFile) = os.path.split(os.path.abspath(sys.argv[0]))
+ thisDir = os.path.normpath(thisDir)
+ if "/".join(thisDir.split(os.sep)[-3:]) != "js/src/builtin":
+ raise RuntimeError("%s must reside in js/src/builtin" % sys.argv[0])
+ topsrcdir = "/".join(thisDir.split(os.sep)[:-3])
+
+ icuDir = os.path.join(topsrcdir, "intl/icu/source")
+ if not os.path.isdir(icuDir):
+ raise RuntimeError("not a directory: %s" % icuDir)
+
+ icuTzDir = os.path.join(topsrcdir, "intl/tzdata/source")
+ if not os.path.isdir(icuTzDir):
+ raise RuntimeError("not a directory: %s" % icuTzDir)
+
+ dateTimeFormatTestDir = os.path.join(topsrcdir, "js/src/tests/Intl/DateTimeFormat")
+ if not os.path.isdir(dateTimeFormatTestDir):
+ raise RuntimeError("not a directory: %s" % dateTimeFormatTestDir)
+
+ tzDir = args.tz
+ if tzDir is not None and not (os.path.isdir(tzDir) or os.path.isfile(tzDir)):
+ raise RuntimeError("not a directory or file: %s" % tzDir)
+ ignoreBackzone = args.ignore_backzone
+ # TODO: Accept or ignore the placeholder time zone "Factory"?
+ ignoreFactory = False
+ out = args.out
+
+ version = icuTzDataVersion(icuTzDir)
+ url = "https://www.iana.org/time-zones/repository/releases/tzdata%s.tar.gz" % version
+
+ print("Arguments:")
+ print("\ttzdata version: %s" % version)
+ print("\ttzdata URL: %s" % url)
+ print("\ttzdata directory|file: %s" % tzDir)
+ print("\tICU directory: %s" % icuDir)
+ print("\tICU timezone directory: %s" % icuTzDir)
+ print("\tIgnore backzone file: %s" % ignoreBackzone)
+ print("\tOutput file: %s" % out)
+ print("")
+
+ def updateFrom(f):
+ if os.path.isfile(f) and tarfile.is_tarfile(f):
+ with tarfile.open(f, "r:*") as tar:
+ processTimeZones(TzDataFile(tar), icuDir, icuTzDir, version, ignoreBackzone, ignoreFactory, out)
+ generateTzDataTests(TzDataFile(tar), version, ignoreBackzone, dateTimeFormatTestDir)
+ elif os.path.isdir(f):
+ processTimeZones(TzDataDir(f), icuDir, icuTzDir, version, ignoreBackzone, ignoreFactory, out)
+ generateTzDataTests(TzDataDir(f), version, ignoreBackzone, dateTimeFormatTestDir)
+ else:
+ raise RuntimeError("unknown format")
+
+ if tzDir is None:
+ print("Downloading tzdata file...")
+ with closing(urllib2.urlopen(url)) as tzfile:
+ fname = urlparse.urlsplit(tzfile.geturl()).path.split("/")[-1]
+ with tempfile.NamedTemporaryFile(suffix=fname) as tztmpfile:
+ print("File stored in %s" % tztmpfile.name)
+ tztmpfile.write(tzfile.read())
+ tztmpfile.flush()
+ updateFrom(tztmpfile.name)
+ else:
+ updateFrom(tzDir)
+
+if __name__ == "__main__":
+ import argparse
+
+ def EnsureHttps(v):
+ if not v.startswith("https:"):
+ raise argparse.ArgumentTypeError("URL protocol must be https: " % v)
+ return v
+
+ parser = argparse.ArgumentParser(description="Update intl data.")
+ subparsers = parser.add_subparsers(help="Select update mode")
+
+ parser_tags = subparsers.add_parser("langtags",
+ help="Update language-subtag-registry")
+ parser_tags.add_argument("--url",
+ metavar="URL",
+ default="https://www.iana.org/assignments/language-subtag-registry",
+ type=EnsureHttps,
+ help="Download url for language-subtag-registry.txt (default: %(default)s)")
+ parser_tags.add_argument("--out",
+ default="IntlData.js",
+ help="Output file (default: %(default)s)")
+ parser_tags.add_argument("file",
+ nargs="?",
+ help="Local language-subtag-registry.txt file, if omitted uses <URL>")
+ parser_tags.set_defaults(func=updateLangTags)
+
+ parser_tz = subparsers.add_parser("tzdata", help="Update tzdata")
+ parser_tz.add_argument("--tz",
+ help="Local tzdata directory or file, if omitted downloads tzdata "
+ "distribution from https://www.iana.org/time-zones/")
+ # ICU doesn't include the backzone file by default, but we still like to
+ # use the backzone time zone names to avoid user confusion. This does lead
+ # to formatting "historic" dates (pre-1970 era) with the wrong time zone,
+ # but that's probably acceptable for now.
+ parser_tz.add_argument("--ignore-backzone",
+ action="store_true",
+ help="Ignore tzdata's 'backzone' file. Can be enabled to generate more "
+ "accurate time zone canonicalization reflecting the actual time "
+ "zones as used by ICU.")
+ parser_tz.add_argument("--out",
+ default="IntlTimeZoneData.h",
+ help="Output file (default: %(default)s)")
+ parser_tz.set_defaults(func=updateTzdata)
+
+ args = parser.parse_args()
+ args.func(args)