From e97429987d3aa723b3b8921ed9e9bf60a5d3be2b Mon Sep 17 00:00:00 2001 From: Eric Skoglund Date: Thu, 19 Feb 2015 15:39:07 +0100 Subject: [PATCH] Bug 1121936 - Implement %TypedArray%.prototyp.{map,filter}. r=evilpie --- js/src/builtin/TypedArray.js | 109 +++++++ .../tests/ecma_6/TypedArray/map-and-filter.js | 297 ++++++++++++++++++ js/src/vm/TypedArrayObject.cpp | 2 + js/xpconnect/tests/chrome/test_xrayToJS.xul | 3 +- 4 files changed, 410 insertions(+), 1 deletion(-) create mode 100644 js/src/tests/ecma_6/TypedArray/map-and-filter.js diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js index dc8c2d97c9c0..26eeeeda51ee 100644 --- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -98,6 +98,70 @@ function TypedArrayFill(value, start = 0, end = undefined) { return O; } +// ES6 draft 32 (2015-02-02) 22.2.3.9 %TypedArray%.prototype.filter(callbackfn[, thisArg]) +function TypedArrayFilter(callbackfn, thisArg = undefined) { + // Step 1. + var O = this; + + // Steps 2-3. + // This function is not generic. + if (!IsObject(O) || !IsTypedArray(O)) { + return callFunction(CallTypedArrayMethodIfWrapped, this, callbackfn, thisArg, + "TypedArrayFilter"); + } + + // Step 4. + var len = TypedArrayLength(O); + + // Step 5. + if (arguments.length === 0) + ThrowError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.filter"); + if (!IsCallable(callbackfn)) + ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn)); + + // Step 6. + var T = thisArg; + + // Step 7. + var defaultConstructor = _ConstructorForTypedArray(O); + + // Steps 8-9. + var C = SpeciesConstructor(O, defaultConstructor); + + // Step 10. + var kept = new List(); + + // Step 12. + var captured = 0; + + // Steps 11, 13 and 13.g. + for (var k = 0; k < len; k++) { + // Steps 13.b-c. + var kValue = O[k]; + // Steps 13.d-e. + var selected = ToBoolean(callFunction(callbackfn, T, kValue, k, O)); + // Step 13.f. + if (selected) { + // Step 13.f.i. + kept.push(kValue); + // Step 13.f.ii. + captured++; + } + } + + // Steps 14-15. + var A = new C(captured); + + // Steps 16 and 17.c. + for (var n = 0; n < captured; n++) { + // Steps 17.a-b. + A[n] = kept[n]; + } + + // Step 18. + return A; +} + // ES6 draft rev28 (2014/10/14) 22.2.3.10 %TypedArray%.prototype.find(predicate[, thisArg]). function TypedArrayFind(predicate, thisArg = undefined) { // This function is not generic. @@ -353,6 +417,51 @@ function TypedArrayLastIndexOf(searchElement, fromIndex = undefined) { return -1; } +// ES6 draft rev32 (2015-02-02) 22.2.3.18 %TypedArray%.prototype.map(callbackfn [, thisArg]). +function TypedArrayMap(callbackfn, thisArg = undefined) { + // Step 1. + var O = this; + + // Steps 2-3. + // This function is not generic. + if (!IsObject(O) || !IsTypedArray(O)) { + return callFunction(CallTypedArrayMethodIfWrapped, this, callbackfn, thisArg, + "TypedArrayMap"); + } + + // Step 4. + var len = TypedArrayLength(O); + + // Step 5. + if (arguments.length === 0) + ThrowError(JSMSG_MISSING_FUN_ARG, 0, '%TypedArray%.prototype.map'); + if (!IsCallable(callbackfn)) + ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn)); + + // Step 6. + var T = thisArg; + + // Step 7. + var defaultConstructor = _ConstructorForTypedArray(O); + + // Steps 8-9. + var C = SpeciesConstructor(O, defaultConstructor); + + // Steps 10-11. + var A = new C(len); + + // Steps 12, 13.a (implicit) and 13.h. + for (var k = 0; k < len; k++) { + // Steps 13.d-e. + var mappedValue = callFunction(callbackfn, T, O[k], k, O); + // Steps 13.f-g. + A[k] = mappedValue; + } + + // Step 14. + return A; +} + // ES6 draft rev30 (2014/12/24) 22.2.3.19 %TypedArray%.prototype.reduce(callbackfn[, initialValue]). function TypedArrayReduce(callbackfn/*, initialValue*/) { // This function is not generic. diff --git a/js/src/tests/ecma_6/TypedArray/map-and-filter.js b/js/src/tests/ecma_6/TypedArray/map-and-filter.js new file mode 100644 index 000000000000..db5c9a1a05c6 --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/map-and-filter.js @@ -0,0 +1,297 @@ +const constructors = [ + Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array +]; + +// Tests for TypedArray#map. +for (var constructor of constructors) { + assertEq(constructor.prototype.map.length, 1); + + // Basic tests. + assertDeepEq(new constructor([1, 3, 5]).map(v => v * 2), new constructor([2,6,10])); + assertDeepEq(new constructor([-1, 13, 5]).map(v => v - 2), new constructor([-3, 11, 3])); + assertDeepEq(new constructor(10).map(v => v), new constructor(10)); + assertDeepEq(new constructor().map(v => v + 1), new constructor); + assertDeepEq(new constructor([1,2,3]).map(v => v), new constructor([1,2,3])); + + var arr = new constructor([1, 2, 3, 4, 5]); + var sum = 0; + var count = 0; + assertDeepEq(arr.map((v, k, o) => { + count++; + sum += v; + assertEq(k, v - 1); + assertEq(o, arr); + return v; + }), arr); + assertEq(sum, 15); + assertEq(count, 5); + + // Test that changing elements that have been visited does not affect the result. + var changeArr = new constructor([1,2,3,4,5]); + assertDeepEq(arr.map((v,k) => { + changeArr[k] = v + 1; + return v; + }), new constructor([1,2,3,4,5])); + + // Tests for `thisArg` argument. + function assertThisArg(thisArg, thisValue) { + // In sloppy mode, `this` could be global object or a wrapper of `thisArg`. + assertDeepEq(arr.map(function(v) { + assertDeepEq(this, thisValue); + return v; + }, thisArg), arr); + + // In strict mode, `this` strictly equals `thisArg`. + assertDeepEq(arr.map(function(v) { + "use strict"; + assertDeepEq(this, thisArg); + return v; + }, thisArg), arr); + + // Passing `thisArg` has no effect if callback is an arrow function. + var self = this; + assertDeepEq(arr.map((v) => { + assertEq(this, self); + return v; + }, thisArg), arr); + } + assertThisArg([1, 2, 3], [1, 2, 3]); + assertThisArg(Object, Object); + assertThisArg(1, Object(1)); + assertThisArg("1", Object("1")); + assertThisArg(false, Object(false)); + assertThisArg(undefined, this); + assertThisArg(null, this); + + // Throw an exception in the callback. + var sum = 0; + var count = 0; + var thrown = false; + try { + arr.map((v, k, o) => { + count++; + sum += v; + assertEq(k, v - 1); + assertEq(o, arr); + if (v === 3) { + throw "map"; + } + return v; + }) + } catch(e) { + assertEq(e, "map"); + thrown = true; + } + assertEq(thrown, true); + assertEq(sum, 6); + assertEq(count, 3); + + // There is no callback or callback is not a function. + assertThrowsInstanceOf(() => { + arr.map(); + }, TypeError); + var invalidCallbacks = [undefined, null, 1, false, "", Symbol(), [], {}, /./]; + invalidCallbacks.forEach(callback => { + assertThrowsInstanceOf(() => { + arr.map(callback); + }, TypeError); + }) + + // Callback is a generator. + arr.map(function*(){ + throw "This line will not be executed"; + }); + + // Called from other globals. + if (typeof newGlobal === "function") { + var map = newGlobal()[constructor.name].prototype.map; + var sum = 0; + assertDeepEq(map.call(new constructor([1, 2, 3]), v => sum += v), new constructor([1,3,6])); + assertEq(sum, 6); + } + + // Throws if `this` isn't a TypedArray. + var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./, + new Proxy(new constructor(), {})]; + invalidReceivers.forEach(invalidReceiver => { + assertThrowsInstanceOf(() => { + constructor.prototype.filter.call(invalidReceiver, () => true); + }, TypeError, "Assert that map fails if this value is not a TypedArray"); + }); + + // Test that the length getter is never called. + assertDeepEq(Object.defineProperty(new constructor([1, 2, 3]), "length", { + get() { + throw new Error("length accessor called"); + } + }).map((b) => b), new constructor([1,2,3])); +} + +// Test For TypedArray#filter. +for (var constructor of constructors) { + assertEq(constructor.prototype.filter.length, 1) + + // Basic tests. + assertDeepEq(new constructor([1,2,3]).filter(x => x == x), new constructor([1,2,3])); + assertDeepEq(new constructor([1,2,3,4]).filter(x => x % 2 == 0), new constructor([2,4])); + assertDeepEq(new constructor([1,2,3,4,5]).filter(x => x < 4), new constructor([1,2,3])); + assertDeepEq(new constructor().filter(x => x * 2 == 4), new constructor()); + + var arr = new constructor([1,2,3,4,5]); + var sum = 0; + var count = 0; + assertDeepEq(arr.filter((v, k, o) => { + count++; + sum += v; + assertEq(k, v - 1); + assertEq(o, arr); + return (v < 4); + }), new constructor([1,2,3])); + assertEq(sum, 15); + assertEq(count, 5); + + // Test that changing elements that have been visited does not affect the result. + var changeArr = new constructor([1,2,3,4,5]); + assertDeepEq(arr.filter((v,k) => { + changeArr[k] = v + 1; + return true; + }), new constructor([1,2,3,4,5])); + + // Tests for `thisArg` argument. + function assertThisArg(thisArg, thisValue) { + // In sloppy mode, `this` could be global object or a wrapper of `thisArg`. + assertDeepEq(arr.filter(function(v) { + assertDeepEq(this, thisValue); + return v; + }, thisArg), arr); + + // In strict mode, `this` strictly equals `thisArg`. + assertDeepEq(arr.filter(function(v) { + "use strict"; + assertDeepEq(this, thisArg); + return v; + }, thisArg), arr); + + // Passing `thisArg` has no effect if callback is an arrow function. + var self = this; + assertDeepEq(arr.filter((v) => { + assertEq(this, self); + return v; + }, thisArg), arr); + } + assertThisArg([1, 2, 3], [1, 2, 3]); + assertThisArg(Object, Object); + assertThisArg(1, Object(1)); + assertThisArg("1", Object("1")); + assertThisArg(false, Object(false)); + assertThisArg(undefined, this); + assertThisArg(null, this); + + // Throw an exception in the callback. + var sum = 0; + var count = 0; + var thrown = false; + try { + arr.filter((v, k, o) => { + count++; + sum += v; + assertEq(k, v - 1); + assertEq(o, arr); + if (v === 3) { + throw "filter"; + } + return v; + }) + } catch(e) { + assertEq(e, "filter"); + thrown = true; + } + assertEq(thrown, true); + assertEq(sum, 6); + assertEq(count, 3); + + // There is no callback or callback is not a function. + assertThrowsInstanceOf(() => { + arr.filter(); + }, TypeError); + var invalidCallbacks = [undefined, null, 1, false, "", Symbol(), [], {}, /./]; + invalidCallbacks.forEach(callback => { + assertThrowsInstanceOf(() => { + arr.filter(callback); + }, TypeError); + }) + + // Callback is a generator. + arr.filter(function*(){ + throw "This line will not be executed"; + }); + + // Called from other globals. + if (typeof newGlobal === "function") { + var filter = newGlobal()[constructor.name].prototype.filter; + var sum = 0; + assertDeepEq(filter.call(new constructor([1, 2, 3]), v => {sum += v; return true}), + new constructor([1,2,3])); + assertEq(sum, 6); + } + + // Throws if `this` isn't a TypedArray. + var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./, + new Proxy(new constructor(), {})]; + invalidReceivers.forEach(invalidReceiver => { + assertThrowsInstanceOf(() => { + constructor.prototype.filter.call(invalidReceiver, () => true); + }, TypeError, "Assert that filter fails if this value is not a TypedArray"); + }); + + // Test that the length getter is never called. + assertDeepEq(Object.defineProperty(new constructor([1, 2, 3]), "length", { + get() { + throw new Error("length accessor called"); + } + }).filter((b) => true), new constructor([1,2,3])); +} + +// Test that changing Array.prototype[Symbol.iterator] does not affect the +// behaviour of filter. See https://bugzilla.mozilla.org/show_bug.cgi?id=1121936#c18 +// for more details. + +// Object conforming to the "iterator" protocol +var obj = { + v: 0, + next: function() { + if (this.v == 5) { + return {done : true, value : this.v }; + } else { + this.v++; + return { done : false, value : this.v }; + } + } +}; + +// save +var old = Array.prototype[Symbol.iterator]; + +Array.prototype[Symbol.iterator] = obj; +assertDeepEq(new Uint16Array([1,2,3]).filter(v => true), new Uint16Array([1,2,3])); + +// restore +Array.prototype[Symbol.iterator] = old; + +// Test that defining accessors on Array.prototype doesn't affect the behaviour +// of filter. See https://bugzilla.mozilla.org/show_bug.cgi?id=1121936#c18 +// for more details. +Object.defineProperty(Array.prototype, 0, {configurable: true, get: function() { return 1; }, set: function() { this.b = 1; }}); +assertDeepEq(new Uint16Array([1,2,3]).filter(v => true), new Uint16Array([1,2,3])); +delete Array.prototype[0]; + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 3e7791502278..f1cec402753b 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -802,12 +802,14 @@ TypedArrayObject::protoFunctions[] = { JS_FN("copyWithin", TypedArrayObject::copyWithin, 2, 0), JS_SELF_HOSTED_FN("every", "TypedArrayEvery", 2, 0), JS_SELF_HOSTED_FN("fill", "TypedArrayFill", 3, 0), + JS_SELF_HOSTED_FN("filter", "TypedArrayFilter", 2, 0), JS_SELF_HOSTED_FN("find", "TypedArrayFind", 2, 0), JS_SELF_HOSTED_FN("findIndex", "TypedArrayFindIndex", 2, 0), JS_SELF_HOSTED_FN("forEach", "TypedArrayForEach", 2, 0), JS_SELF_HOSTED_FN("indexOf", "TypedArrayIndexOf", 2, 0), JS_SELF_HOSTED_FN("join", "TypedArrayJoin", 1, 0), JS_SELF_HOSTED_FN("lastIndexOf", "TypedArrayLastIndexOf", 2, 0), + JS_SELF_HOSTED_FN("map", "TypedArrayMap", 2, 0), JS_SELF_HOSTED_FN("reduce", "TypedArrayReduce", 1, 0), JS_SELF_HOSTED_FN("reduceRight", "TypedArrayReduceRight", 1, 0), JS_SELF_HOSTED_FN("reverse", "TypedArrayReverse", 0, 0), diff --git a/js/xpconnect/tests/chrome/test_xrayToJS.xul b/js/xpconnect/tests/chrome/test_xrayToJS.xul index 6ab39d9c7146..6704dfe9b254 100644 --- a/js/xpconnect/tests/chrome/test_xrayToJS.xul +++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul @@ -177,7 +177,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=933681 gPrototypeProperties['TypedArray'] = ["length", "buffer", "byteLength", "byteOffset", Symbol.iterator, "subarray", "set", "copyWithin", "find", "findIndex", "forEach","indexOf", "lastIndexOf", "reverse", - "join", "every", "some", "reduce", "reduceRight", "entries", "keys", "values", "slice"]; + "join", "every", "some", "reduce", "reduceRight", "entries", "keys", "values", "slice", + "map", "filter"]; if (isNightlyBuild) { gPrototypeProperties['TypedArray'].push('includes'); }