spread.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
  6. function p(x) {
  7. WScript.Echo(x);
  8. }
  9. var tests = [
  10. {
  11. name: "Testing spread array literals (all should be the same)",
  12. body: function () {
  13. var a = [1, 2];
  14. var b = [3];
  15. var c = [];
  16. var d = [4, 5, 6];
  17. var joined = [];
  18. joined[0] = [1, 2, 3, 4, 5, 6];
  19. joined[1] = [...a, ...b, ...c, ...d];
  20. joined[2] = [1, 2, ...b, ...d];
  21. joined[3] = [...[1, 2], ...b, ...[], ...[...d]];
  22. joined[4] = [...[], ...joined[2], ...[], ...c];
  23. joined[5] = [...a, ...b, ...c, 4, 5, 6];
  24. for (var i = 0; i < joined.length; ++i) {
  25. assert.areEqual(joined[i], joined[0], "joined[" + i + "] + === + joined[" + 0 + "]");
  26. }
  27. }
  28. },
  29. {
  30. name: "Testing call with spread args (all should be the same)",
  31. body: function() {
  32. var a = [1, 2];
  33. var b = [3];
  34. var c = [];
  35. var d = [4, 5, 6];
  36. function quad(a, b, c, x) {
  37. return a*x*x + b*x + c;
  38. }
  39. var result = [];
  40. result[0] = quad(1, 2, 3, 4);
  41. result[1] = quad(...a, ...b, ...c, 4);
  42. result[2] = quad(...a, ...b, ...[4]);
  43. result[3] = quad(...[...a, ...b, ...c], 4);
  44. for (var j = 0; j < result.length; ++j) {
  45. assert.areEqual(result[j], result[0], "result[" + j + "] === result[0]");
  46. }
  47. }
  48. },
  49. {
  50. name: "Testing spread with a lot of padding numbers",
  51. body: function () {
  52. function quad(a, b, c, x) {
  53. return a*x*x + b*x + c;
  54. }
  55. var e = [7, 8];
  56. var f = [12, 13];
  57. var largeLiteral = [1, 2, 3, 4, 5, 6, ...e, 9, 10, 11, ...f, 14, 15, 16, 17, 18, 19, 20];
  58. var largeLiteralFull = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
  59. assert.areEqual(largeLiteral, largeLiteralFull, "Array literals equal");
  60. assert.areEqual(quad(1, 2, 3, 4, 5, 6, ...e, 9, 10, 11, ...f, 14, 15, 16, 17, 18, 19, 20), quad(1, 2, 3, 4, 5, 6), "Calls equal");
  61. }
  62. },
  63. {
  64. name: "Testing array literals with gaps",
  65. body: function () {
  66. assert.areEqual([1, undefined, 3], [...[1,,3]], "Spreading missing values gives undefined in the new array elements");
  67. assert.areEqual([undefined, undefined, 1, undefined, 2, 3, 1, 2, 3, 3], [...[...[,,1,,2,3], ...[1, 2, 3], 3]], "Spreading missing values gives undefined in the new array elements");
  68. }
  69. },
  70. {
  71. name: "Testing call spread args with gaps",
  72. body: function () {
  73. function spreadValid1(a, b, c, d) {
  74. assert.areEqual(1, a, "Spreading call arguments with array gaps: a is 1");
  75. assert.areEqual(undefined, b, "Spreading call arguments with array gaps: b is undefined");
  76. assert.areEqual(3, c, "Spreading call arguments with array gaps: c is 3");
  77. assert.areEqual(undefined, d, "Spreading call arguments with array gaps: d is undefined");
  78. }
  79. spreadValid1(...[1, , 3,]);
  80. function spreadValid2(e, f, g, h) {
  81. assert.areEqual(4, e, "Spreading nested call arguments with array gaps: e is 4");
  82. assert.areEqual(undefined, f, "Spreading nested call arguments with array gaps: f is undefined");
  83. assert.areEqual(undefined, g, "Spreading nested call arguments with array gaps: g is undefined");
  84. assert.areEqual(8, h, "Spreading nested call arguments with array gaps: h is 8");
  85. }
  86. spreadValid2(...[4, , ...[, 8]]);
  87. }
  88. },
  89. {
  90. name: "Testing spread with string literals",
  91. body: function () {
  92. assert.areEqual(["s", "t", "r", "i", "n", "g"], [..."string"], "Spreading a string creates a character array of the string");
  93. }
  94. },
  95. {
  96. name: "BLUE 491118: Spread outside of a call or array literal",
  97. body: function () {
  98. assert.throws(function () { eval("var a = []; ...a") }, SyntaxError, "Spread outside of a call or array literal");
  99. }
  100. },
  101. {
  102. name: "BLUE 511620: Calling spread operation inside string template results in crash",
  103. body: function () {
  104. function foo(a,b){}
  105. var demo = "hello";
  106. assert.throws(function() { eval("var strAdd = foo`${...demo}`;"); }, SyntaxError, "Spread inside string template");
  107. }
  108. },
  109. {
  110. name: "BLUE 490915: ... is a valid operator but ...... (2 spreads combined) is not",
  111. body: function () {
  112. assert.throws(function () { eval("var a = []; [......a];") }, SyntaxError, "Two spreads combined is a syntax error");
  113. assert.throws(function () { eval("function foo(){};var a = []; foo(......a);") }, SyntaxError, "Two spreads combined is a syntax error");
  114. }
  115. },
  116. {
  117. name: "BLUE 522366: Spreading null or undefined should throw TypeError instead of RuntimeError",
  118. body: function () {
  119. function foo(bar) { return bar; }
  120. assert.throws(function () { foo(...null); }, TypeError);
  121. assert.throws(function () { [...null]; }, TypeError);
  122. assert.throws(function () { foo(...undefined); }, TypeError);
  123. assert.throws(function () { [...undefined]; }, TypeError);
  124. }
  125. },
  126. {
  127. name: "BLUE 522630: Assignment expresion should be evaluated fisrt and then the results should be spread",
  128. body: function () {
  129. let b = { get x() { return "abc"; } }
  130. a = [1, ...b.x + ""];
  131. assert.areEqual([1,"a","b","c"], a);
  132. }
  133. },
  134. {
  135. name: "Spread functionality in non-standard call types",
  136. body: function () {
  137. var shortArray = [1,2,3]; // Doesn't use _alloca()
  138. var longArray = [5,3,8,2,4,8,5,3,1,912341234,33543,12987,3476,-2134124,3245235]; // Uses _alloca()
  139. // Ctor tests
  140. assert.areEqual(shortArray, new Array(...shortArray));
  141. assert.areEqual(longArray, new Array(...longArray));
  142. function Storage(a,b,c,d,e,f,g,h,i) {
  143. this.a = a;
  144. this.b = b;
  145. this.c = c;
  146. this.d = d;
  147. this.e = e;
  148. this.f = f;
  149. this.g = g;
  150. this.h = h;
  151. this.i = i;
  152. }
  153. assert.areEqual(new Storage(1,2,3), new Storage(...shortArray));
  154. assert.areEqual(new Storage(5,3,8,2,4,8,5,3,1), new Storage(...longArray));
  155. // Eval
  156. assert.areEqual("hello", eval(...['"hello"']));
  157. assert.areEqual(longArray[0], eval(...longArray));
  158. }
  159. },
  160. {
  161. name: "BLUE: 521584:[ES6][Spread] - Spread inside eval is considered valid without quotes. Currently crashes",
  162. body: function () {
  163. assert.areEqual(undefined, eval(...[]));
  164. assert.areEqual(undefined, eval(...[], ...[]));
  165. assert.areEqual(undefined, eval(...[], ...[], ...[]));
  166. assert.areEqual(undefined, eval(...[], ...[], ...[], ...[]));
  167. assert.areEqual(123123, eval(...[], ...["123123"]));
  168. assert.areEqual(123123, eval(...[], ...["123123"], ...[]));
  169. assert.areEqual(123123, eval(...[], ...[], ...["123123"], ...[]));
  170. assert.areEqual(123123, eval(...[], ...[], ...[], ...[], ...["123123"]));
  171. assert.areEqual(123123, eval(...["123123"], ...[], ...[], ...[], ...[]));
  172. assert.areEqual(undefined, eval.call(...[]));
  173. assert.areEqual(undefined, eval.call(...[], ...["123123"]));
  174. }
  175. },
  176. {
  177. name: "Spread with side effects",
  178. body: function () {
  179. var i = 0;
  180. eval(...["i++"]);
  181. assert.areEqual(1, i);
  182. eval(...[], ...["i += 1"]);
  183. assert.areEqual(2, i);
  184. eval(...[], ...["i = i - i"], ...[]);
  185. assert.areEqual(0, i);
  186. }
  187. },
  188. {
  189. name: "BLUE 584814: Instr::HasFixedFunctionAddressTarget() assertion",
  190. body: function () {
  191. function badFunc() {
  192. function shapeyConstructor(iijcze) {
  193. if (((NaN += /x/).b)) iijcze.d = (new((4277))(new[1, , ](/x/, ''), ...y));
  194. }
  195. for (var z in [/x/, /x/, true, ]) {
  196. let drjotv = shapeyConstructor(z);
  197. }
  198. }
  199. badFunc(); badFunc();
  200. }
  201. },
  202. {
  203. name: "BLUE 582720: Spread calls with invalid functions crash in native",
  204. body: function () {
  205. function badFunc() {
  206. (12345)(...[]);
  207. }
  208. assert.throws(function () { badFunc(); badFunc(); });
  209. }
  210. },
  211. {
  212. name: "BLUE 589583: CallIPut with spread causes an assert",
  213. body: function () {
  214. function a() {};
  215. var x = [];
  216. assert.throws(function() { eval('a(...x)--'); }, ReferenceError, "Spread with CallIPut throws a ReferenceError");
  217. }
  218. },
  219. /*
  220. A fix for an unsafe optimization makes this portion of the test time out.
  221. {
  222. name: "BLUE 596934, 597412: Incorrect spread argument length handling",
  223. body: function () {
  224. function a() {}
  225. // The implemented version of the spec allows overflow of the length when converting to UInt32.
  226. assert.throws(function () { a(...new Array (0x50505050)); }, RangeError, "Very large array throws RangeError");
  227. try {
  228. a(...new Array(1 << 24 - 1)); // No RangeError throw (max call args)
  229. } catch (e) {
  230. // This many args will blow out the stack. But it shouldn't be a length limitation.
  231. assert.areNotEqual(e.constructor, RangeError, "Max call args should not throw a RangeError");
  232. }
  233. assert.throws(function () { a(...new Array(1 << 24)); }, RangeError, "Array size greater than max call arg count throws RangeError");
  234. assert.throws(function () { a(...new Array(3), ...new Array(1 << 32 - 2)); }, RangeError, "Total spread size greater than max call arg count throws RangeError");
  235. }
  236. },
  237. */
  238. {
  239. name: "MSRC 34309: Guard against getter in prototype",
  240. body: function () {
  241. var x = [0x40];
  242. x.length = 0x9;
  243. Object.defineProperty(Array.prototype, 1, {
  244. get: function() {
  245. x.length = 0;
  246. }
  247. });
  248. var f = function(){
  249. assert.areEqual(arguments.length, 2, "Changing length of x during spreading should truncate the spread.");
  250. }
  251. f(...x);
  252. }
  253. },
  254. {
  255. name: "BLUE 611774: Spread with a prefix operator is allowed anywhere",
  256. body: function () {
  257. assert.throws(function () { eval('++...window, z;'); }, SyntaxError, "Invalid use of the ... operator");
  258. }
  259. },
  260. {
  261. name: "OS 5204357: Corner case: user changes %ArrayIteratorPrototype%.next property and we should call it",
  262. body: function () {
  263. var overrideCalled = false;
  264. var arrayIteratorProto = Object.getPrototypeOf([][Symbol.iterator]());
  265. var arrayIteratorProtoNext = arrayIteratorProto.next;
  266. function testArrayIteratorProto() {
  267. var a = [1];
  268. function f() {}
  269. f(...a);
  270. assert.isTrue(overrideCalled, "Spread of a in call to f should have invoked the overridden %ArrayIteratorPrototype%.next method");
  271. overrideCalled = false;
  272. var b = [...a];
  273. assert.isTrue(overrideCalled, "Spread of a in array initializer should have invoked the overridden %ArrayIteratorPrototype%.next method");
  274. overrideCalled = false;
  275. }
  276. var overrideFunc = function () {
  277. overrideCalled = true;
  278. return arrayIteratorProtoNext.apply(this, arguments);
  279. };
  280. arrayIteratorProto.next = overrideFunc;
  281. testArrayIteratorProto();
  282. // Restore the original prototype .next
  283. arrayIteratorProto.next = arrayIteratorProtoNext;
  284. function getIterableObjNextDesc() {
  285. return {
  286. get: function next() {
  287. overrideCalled = true;
  288. return function () {
  289. return {
  290. value: 0,
  291. done: 1
  292. };
  293. };
  294. }
  295. };
  296. }
  297. // Change built-in array iterator's next getter function
  298. var builtinArrayPrototypeIteratorNextDesc = Object.getOwnPropertyDescriptor(arrayIteratorProto, "next");
  299. Object.defineProperty(arrayIteratorProto, "next", getIterableObjNextDesc());
  300. testArrayIteratorProto();
  301. Object.defineProperty(arrayIteratorProto, "next", builtinArrayPrototypeIteratorNextDesc);
  302. }
  303. },
  304. {
  305. name: "Corner case: Spread of an array with an accessor property (ES5Array) should call that getter and recognize a change in length during iteration",
  306. body: function () {
  307. var result = [];
  308. function foo(a, b, c, d) {
  309. result.push(a);
  310. result.push(b);
  311. result.push(c);
  312. result.push(d);
  313. }
  314. var a = [5, 6, 7];
  315. a.two = 7;
  316. Object.defineProperty(a, '2', { get: function () { this[3] = 8; return this.two; } });
  317. foo(...a);
  318. assert.areEqual(4, result.length, "Spread for the function argument called the getter and caused the length to be updated to four");
  319. assert.areEqual(5, result[0], "Spread for the function argument passed the first element of a as the first argument");
  320. assert.areEqual(6, result[1], "Spread for the function argument passed the second element of a as the second argument");
  321. assert.areEqual(7, result[2], "Spread for the function argument called the getter of the third element of a and passed the result as the third argument");
  322. assert.areEqual(8, result[3], "Spread for the function argument recognized the new length of four and passed the fourth element of a as the fourth argument");
  323. a = [5, 6, 7];
  324. a.two = 7;
  325. Object.defineProperty(a, '2', { get: function () { this[3] = 8; return this.two; } });
  326. var b = [...a];
  327. result = b;
  328. assert.areEqual(4, result.length, "Spread for the array initializer called the getter and caused the length to be updated to four");
  329. assert.areEqual(5, result[0], "Spread for the array initializer copied the first element of a to the first element of b");
  330. assert.areEqual(6, result[1], "Spread for the array initializer copied the second element of a to the second element of b");
  331. assert.areEqual(7, result[2], "Spread for the array initializer called the getter of the third element of a and copied the result to the third element of b");
  332. assert.areEqual(8, result[3], "Spread for the array initializer recognized the new length of four and copied the fourth element of a to the fourth element of b");
  333. }
  334. },
  335. {
  336. name: "Corner case: Spread of an arguments object with an accessor property (ES5HeapArgumentsObject) should call that getter and recognize a change in length during iteration",
  337. body: function () {
  338. var result = [];
  339. function foo(a, b, c, d) {
  340. result.push(a);
  341. result.push(b);
  342. result.push(c);
  343. result.push(d);
  344. }
  345. function bar() {
  346. arguments.two = 7;
  347. // Note arguments object does not change its length like an array when new elements are added,
  348. // but its length is writable and we can change it ourselves
  349. Object.defineProperty(arguments, '2', { get: function () { this[3] = 8; this.length++; return this.two; } });
  350. foo(...arguments);
  351. }
  352. bar(5, 6, 7);
  353. assert.areEqual(4, result.length, "Spread for the function argument called the getter and caused the length to be updated to four");
  354. assert.areEqual(5, result[0], "Spread for the function argument passed the first element of a as the first argument");
  355. assert.areEqual(6, result[1], "Spread for the function argument passed the second element of a as the second argument");
  356. assert.areEqual(7, result[2], "Spread for the function argument called the getter of the third element of a and passed the result as the third argument");
  357. assert.areEqual(8, result[3], "Spread for the function argument recognized the new length of four and passed the fourth element of a as the fourth argument");
  358. result = [];
  359. function fuz() {
  360. arguments.two = 7;
  361. // Note arguments object does not change its length like an array when new elements are added,
  362. // but its length is writable and we can change it ourselves
  363. Object.defineProperty(arguments, '2', { get: function () { this[3] = 8; this.length++; return this.two; } });
  364. var b = [...arguments];
  365. result = b;
  366. }
  367. fuz(5, 6, 7);
  368. assert.areEqual(4, result.length, "Spread for the array initializer called the getter and caused the length to be updated to four");
  369. assert.areEqual(5, result[0], "Spread for the array initializer copied the first element of a to the first element of b");
  370. assert.areEqual(6, result[1], "Spread for the array initializer copied the second element of a to the second element of b");
  371. assert.areEqual(7, result[2], "Spread for the array initializer called the getter of the third element of a and copied the result to the third element of b");
  372. assert.areEqual(8, result[3], "Spread for the array initializer recognized the new length of four and copied the fourth element of a to the fourth element of b");
  373. }
  374. },
  375. {
  376. name: "OS 2970825: Spread should not be allowed with unary operators",
  377. body: function () {
  378. function foo() {}
  379. assert.throws(function () { eval("foo(++...[1,2,3]);"); }, SyntaxError, "Spread with unary operator throws a syntax error", "Unexpected ... operator");
  380. assert.throws(function () { eval("foo(typeof ...[1,2,3]);"); }, SyntaxError, "Spread with keyword unary operator throws a syntax error", "Unexpected ... operator");
  381. assert.throws(function () { eval("foo(!!...[1,2,3]);"); }, SyntaxError, "Spread with chained unary operators throws a syntax error", "Unexpected ... operator");
  382. }
  383. },
  384. {
  385. name: "call scenario - second spread is changing the first spread's length",
  386. body: function () {
  387. function foo() {
  388. var args = [...arguments];
  389. assert.areEqual([101, 102, 201], args, "2 values from the first spread and 1 value from the second spread is expected");
  390. }
  391. var first = [101, 102];
  392. var obj = {};
  393. Object.defineProperty(obj, '2', {get : function() {
  394. assert.fail('this should not have called')
  395. return 103;
  396. }});
  397. var second = [];
  398. second.length = 1;
  399. var getterCalled = false;
  400. Object.defineProperty(second, '0', {get : function() {
  401. // Changing the state of the first spread
  402. first.__proto__ = obj;
  403. first.length = 3;
  404. getterCalled = true;
  405. return 201;
  406. }});
  407. foo(...first, ...second);
  408. assert.isTrue(getterCalled, "getter of the second spread is executed");
  409. }
  410. },
  411. {
  412. name: "array scenario - second spread is changing the first spread's length",
  413. body: function () {
  414. var first = [101, 102];
  415. var obj = {};
  416. Object.defineProperty(obj, '2', {get : function() {
  417. assert.fail('this should not have called')
  418. return 103;
  419. }});
  420. var second = [];
  421. second.length = 1;
  422. var getterCalled = false;
  423. Object.defineProperty(second, '0', {get : function() {
  424. // Changing the state of the first spread
  425. first.__proto__ = obj;
  426. first.length = 3;
  427. getterCalled = true;
  428. return 201;
  429. }});
  430. var result = [...first, ...second];
  431. assert.areEqual([101, 102, 201], result, "2 values from the first spread and 1 value from the second spread is expected");
  432. assert.isTrue(getterCalled, "getter of the second spread is executed");
  433. }
  434. },
  435. {
  436. name: "typedarray scenario - second spread is changing value of first spread which is a typedarray",
  437. body: function () {
  438. var first = new Uint32Array([101, 102]);
  439. var second = [];
  440. second.length = 1;
  441. var getterCalled = false;
  442. Object.defineProperty(second, '0', {get : function() {
  443. // Changing the state of the first spread
  444. first[0] = 11; // This should not affect the resultant spread.
  445. getterCalled = true;
  446. return 201;
  447. }});
  448. var result = [...first, ...second];
  449. assert.areEqual([101, 102, 201], result, "2 values from the first spread and 1 value from the second spread is expected");
  450. assert.isTrue(getterCalled, "getter of the second spread is executed");
  451. }
  452. }
  453. ];
  454. testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });