diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /js/src/builtin | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | uxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'js/src/builtin')
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 0000000000..54b47b72f5 --- /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 0000000000..08777fd512 --- /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 0000000000..adb6fb986a --- /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 0000000000..d0f20b5fd7 --- /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 0000000000..6983d26859 --- /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 0000000000..b0fd727eb2 --- /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 0000000000..87321ba04a --- /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 0000000000..03a75d5999 --- /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 0000000000..1b38ede561 --- /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 0000000000..bd15496605 --- /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 0000000000..990a4acdb7 --- /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 0000000000..54764605bc --- /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 0000000000..493062c1c6 --- /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 0000000000..269cf9f93a --- /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 0000000000..bb7d22109f --- /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 0000000000..735eec7a06 --- /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 0000000000..432364614b --- /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 0000000000..c496cfb778 --- /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 0000000000..9473e6b709 --- /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 0000000000..7b70a7fe8b --- /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 0000000000..3bfc8f60b1 --- /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 0000000000..d0ed8ed08a --- /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 0000000000..07b2be57ab --- /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 0000000000..cd4ac122c9 --- /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 0000000000..09512be369 --- /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 0000000000..a7440aec72 --- /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 0000000000..0d30a1c6b0 --- /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 0000000000..ecd0d0982d --- /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 0000000000..59c97e5299 --- /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 0000000000..bb47786316 --- /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 0000000000..704cbab0be --- /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 0000000000..2f509a2268 --- /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 0000000000..24fdaa2547 --- /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 0000000000..e3a343452d --- /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 0000000000..748ff7351b --- /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 0000000000..80a4bb5bde --- /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, ®exp->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 0000000000..715656f402 --- /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 0000000000..1ffea01050 --- /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 0000000000..fbe50a3f9c --- /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 0000000000..edc2e2056c --- /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 0000000000..2383922db8 --- /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 0000000000..393e7df740 --- /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 0000000000..b57c172691 --- /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 0000000000..accc701201 --- /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 0000000000..660ab079fd --- /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 0000000000..4202d0de63 --- /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) + '"'; + 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 0000000000..cf48402d69 --- /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 0000000000..0f204b494a --- /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 0000000000..5bc69a3467 --- /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 0000000000..e60b7d4dbd --- /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 0000000000..4d2d6488f4 --- /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 0000000000..b7297c8944 --- /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 0000000000..6da62b6567 --- /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 0000000000..c4ddee486b --- /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 0000000000..41f30ab3e6 --- /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 0000000000..bfb1fe7f45 --- /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 0000000000..066a72bfe3 --- /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 0000000000..555b9e03a7 --- /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 0000000000..c9b95cea39 --- /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 0000000000..eb7c2378f8 --- /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 0000000000..7ea3f2fef1 --- /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 0000000000..0a6ff33f36 --- /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 0000000000..c642aebd51 --- /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 0000000000..b81d5951f2 --- /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) |