1// Copyright 2014 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5var global = this;
6var globalProto = Object.getPrototypeOf(global);
7
8// Number of objects being tested. There is an assert ensuring this is correct.
9var objectCount = 21;
10
11
12function runTest(f) {
13  function restore(object, oldProto) {
14    delete object[Symbol.unscopables];
15    delete object.x;
16    delete object.x_;
17    delete object.y;
18    delete object.z;
19    Object.setPrototypeOf(object, oldProto);
20  }
21
22  function getObject(i) {
23    var objects = [
24      {},
25      [],
26      function() {},
27      function() {
28        return arguments;
29      }(),
30      function() {
31        'use strict';
32        return arguments;
33      }(),
34      Object(1),
35      Object(true),
36      Object('bla'),
37      new Date,
38      new RegExp,
39      new Set,
40      new Map,
41      new WeakMap,
42      new WeakSet,
43      new ArrayBuffer(10),
44      new Int32Array(5),
45      Object,
46      Function,
47      Date,
48      RegExp,
49      global
50    ];
51
52    assertEquals(objectCount, objects.length);
53    return objects[i];
54  }
55
56  // Tests depends on this not being there to start with.
57  delete Array.prototype[Symbol.unscopables];
58
59  if (f.length === 1) {
60    for (var i = 0; i < objectCount; i++) {
61      var object = getObject(i);
62      var oldObjectProto = Object.getPrototypeOf(object);
63      f(object);
64      restore(object, oldObjectProto);
65    }
66  } else {
67    for (var i = 0; i < objectCount; i++) {
68      for (var j = 0; j < objectCount; j++) {
69        var object = getObject(i);
70        var proto = getObject(j);
71        if (object === proto) {
72          continue;
73        }
74        var oldObjectProto = Object.getPrototypeOf(object);
75        var oldProtoProto = Object.getPrototypeOf(proto);
76        f(object, proto);
77        restore(object, oldObjectProto);
78        restore(proto, oldProtoProto);
79      }
80    }
81  }
82}
83
84// Test array first, since other tests are changing
85// Array.prototype[Symbol.unscopables].
86function TestArrayPrototypeUnscopables() {
87  var descr = Object.getOwnPropertyDescriptor(Array.prototype,
88                                              Symbol.unscopables);
89  assertFalse(descr.enumerable);
90  assertFalse(descr.writable);
91  assertTrue(descr.configurable);
92  assertEquals(null, Object.getPrototypeOf(descr.value));
93
94  var copyWithin = 'local copyWithin';
95  var entries = 'local entries';
96  var fill = 'local fill';
97  var find = 'local find';
98  var findIndex = 'local findIndex';
99  var keys = 'local keys';
100  var values = 'local values';
101
102  var array = [];
103  array.toString = 42;
104
105  with (array) {
106    assertEquals('local copyWithin', copyWithin);
107    assertEquals('local entries', entries);
108    assertEquals('local fill', fill);
109    assertEquals('local find', find);
110    assertEquals('local findIndex', findIndex);
111    assertEquals('local keys', keys);
112    assertEquals('local values', values);
113    assertEquals(42, toString);
114  }
115}
116TestArrayPrototypeUnscopables();
117
118
119
120function TestBasics(object) {
121  var x = 1;
122  var y = 2;
123  var z = 3;
124  object.x = 4;
125  object.y = 5;
126
127  with (object) {
128    assertEquals(4, x);
129    assertEquals(5, y);
130    assertEquals(3, z);
131  }
132
133  var truthyValues = [true, 1, 'x', {}, Symbol()];
134  for (var truthyValue of truthyValues) {
135    object[Symbol.unscopables] = {x: truthyValue};
136    with (object) {
137      assertEquals(1, x);
138      assertEquals(5, y);
139      assertEquals(3, z);
140    }
141  }
142
143  var falsyValues = [false, 0, -0, NaN, '', null, undefined];
144  for (var falsyValue of falsyValues) {
145    object[Symbol.unscopables] = {x: falsyValue, y: true};
146    with (object) {
147      assertEquals(4, x);
148      assertEquals(2, y);
149      assertEquals(3, z);
150    }
151  }
152
153  for (var xFalsy of falsyValues) {
154    for (var yFalsy of falsyValues) {
155      object[Symbol.unscopables] = {x: xFalsy, y: yFalsy};
156      with (object) {
157        assertEquals(4, x);
158        assertEquals(5, y);
159        assertEquals(3, z);
160      }
161    }
162  }
163}
164runTest(TestBasics);
165
166
167function TestUnscopableChain(object) {
168  var x = 1;
169  object.x = 2;
170
171  with (object) {
172    assertEquals(2, x);
173  }
174
175  object[Symbol.unscopables] = {
176    __proto__: {x: true}
177  };
178  with (object) {
179    assertEquals(1, x);
180  }
181
182  object[Symbol.unscopables] = {
183    __proto__: {x: undefined}
184  };
185  with (object) {
186    assertEquals(2, x);
187  }
188}
189runTest(TestUnscopableChain);
190
191
192function TestBasicsSet(object) {
193  var x = 1;
194  object.x = 2;
195
196  with (object) {
197    assertEquals(2, x);
198  }
199
200  object[Symbol.unscopables] = {x: true};
201  with (object) {
202    assertEquals(1, x);
203    x = 3;
204    assertEquals(3, x);
205  }
206
207  assertEquals(3, x);
208  assertEquals(2, object.x);
209}
210runTest(TestBasicsSet);
211
212
213function TestOnProto(object, proto) {
214  var x = 1;
215  var y = 2;
216  var z = 3;
217  proto.x = 4;
218
219  Object.setPrototypeOf(object, proto);
220  object.y = 5;
221
222  with (object) {
223    assertEquals(4, x);
224    assertEquals(5, y);
225    assertEquals(3, z);
226  }
227
228  proto[Symbol.unscopables] = {x: true};
229  with (object) {
230    assertEquals(1, x);
231    assertEquals(5, y);
232    assertEquals(3, z);
233  }
234
235  object[Symbol.unscopables] = {y: true};
236  with (object) {
237    assertEquals(4, x);
238    assertEquals(2, y);
239    assertEquals(3, z);
240  }
241
242  proto[Symbol.unscopables] = {y: true};
243  object[Symbol.unscopables] = {x: true};
244  with (object) {
245    assertEquals(1, x);
246    assertEquals(5, y);
247    assertEquals(3, z);
248  }
249
250  proto[Symbol.unscopables] = {y: true};
251  object[Symbol.unscopables] = {x: true, y: undefined};
252  with (object) {
253    assertEquals(1, x);
254    assertEquals(5, y);
255    assertEquals(3, z);
256  }
257}
258runTest(TestOnProto);
259
260
261function TestSetBlockedOnProto(object, proto) {
262  var x = 1;
263  object.x = 2;
264
265  with (object) {
266    assertEquals(2, x);
267  }
268
269  Object.setPrototypeOf(object, proto);
270  proto[Symbol.unscopables] = {x: true};
271  with (object) {
272    assertEquals(1, x);
273    x = 3;
274    assertEquals(3, x);
275  }
276
277  assertEquals(3, x);
278  assertEquals(2, object.x);
279}
280runTest(TestSetBlockedOnProto);
281
282
283function TestNonObject(object) {
284  var x = 1;
285  var y = 2;
286  object.x = 3;
287  object.y = 4;
288
289  object[Symbol.unscopables] = 'xy';
290  with (object) {
291    assertEquals(3, x);
292    assertEquals(4, y);
293  }
294
295  object[Symbol.unscopables] = null;
296  with (object) {
297    assertEquals(3, x);
298    assertEquals(4, y);
299  }
300}
301runTest(TestNonObject);
302
303
304function TestChangeDuringWith(object) {
305  var x = 1;
306  var y = 2;
307  object.x = 3;
308  object.y = 4;
309
310  with (object) {
311    assertEquals(3, x);
312    assertEquals(4, y);
313    object[Symbol.unscopables] = {x: true};
314    assertEquals(1, x);
315    assertEquals(4, y);
316  }
317}
318runTest(TestChangeDuringWith);
319
320
321function TestChangeDuringWithWithPossibleOptimization(object) {
322  var x = 1;
323  object.x = 2;
324  with (object) {
325    for (var i = 0; i < 1000; i++) {
326      if (i === 500) object[Symbol.unscopables] = {x: true};
327      assertEquals(i < 500 ? 2: 1, x);
328    }
329  }
330}
331TestChangeDuringWithWithPossibleOptimization({});
332
333
334function TestChangeDuringWithWithPossibleOptimization2(object) {
335  var x = 1;
336  object.x = 2;
337  object[Symbol.unscopables] = {x: true};
338  with (object) {
339    for (var i = 0; i < 1000; i++) {
340      if (i === 500) delete object[Symbol.unscopables];
341      assertEquals(i < 500 ? 1 : 2, x);
342    }
343  }
344}
345TestChangeDuringWithWithPossibleOptimization2({});
346
347
348function TestChangeDuringWithWithPossibleOptimization3(object) {
349  var x = 1;
350  object.x = 2;
351  object[Symbol.unscopables] = {};
352  with (object) {
353    for (var i = 0; i < 1000; i++) {
354      if (i === 500) object[Symbol.unscopables].x = true;
355      assertEquals(i < 500 ? 2 : 1, x);
356    }
357  }
358}
359TestChangeDuringWithWithPossibleOptimization3({});
360
361
362function TestChangeDuringWithWithPossibleOptimization4(object) {
363  var x = 1;
364  object.x = 2;
365  object[Symbol.unscopables] = {x: true};
366  with (object) {
367    for (var i = 0; i < 1000; i++) {
368      if (i === 500) delete object[Symbol.unscopables].x;
369      assertEquals(i < 500 ? 1 : 2, x);
370    }
371  }
372}
373TestChangeDuringWithWithPossibleOptimization4({});
374
375
376function TestChangeDuringWithWithPossibleOptimization4(object) {
377  var x = 1;
378  object.x = 2;
379  object[Symbol.unscopables] = {x: true};
380  with (object) {
381    for (var i = 0; i < 1000; i++) {
382      if (i === 500) object[Symbol.unscopables].x = undefined;
383      assertEquals(i < 500 ? 1 : 2, x);
384    }
385  }
386}
387TestChangeDuringWithWithPossibleOptimization4({});
388
389
390function TestAccessorReceiver(object, proto) {
391  var x = 'local';
392
393  Object.defineProperty(proto, 'x', {
394    get: function() {
395      assertEquals(object, this);
396      return this.x_;
397    },
398    configurable: true
399  });
400  proto.x_ = 'proto';
401
402  Object.setPrototypeOf(object, proto);
403  proto.x_ = 'object';
404
405  with (object) {
406    assertEquals('object', x);
407  }
408}
409runTest(TestAccessorReceiver);
410
411
412function TestUnscopablesGetter(object) {
413  // This test gets really messy when object is the global since the assert
414  // functions are properties on the global object and the call count gets
415  // completely different.
416  if (object === global) return;
417
418  var x = 'local';
419  object.x = 'object';
420
421  var callCount = 0;
422  Object.defineProperty(object, Symbol.unscopables, {
423    get: function() {
424      callCount++;
425      return {};
426    },
427    configurable: true
428  });
429  with (object) {
430    assertEquals('object', x);
431  }
432  // Once for HasBinding
433  assertEquals(1, callCount);
434
435  callCount = 0;
436  Object.defineProperty(object, Symbol.unscopables, {
437    get: function() {
438      callCount++;
439      return {x: true};
440    },
441    configurable: true
442  });
443  with (object) {
444    assertEquals('local', x);
445  }
446  // Once for HasBinding
447  assertEquals(1, callCount);
448
449  callCount = 0;
450  Object.defineProperty(object, Symbol.unscopables, {
451    get: function() {
452      callCount++;
453      return callCount == 1 ? {} : {x: true};
454    },
455    configurable: true
456  });
457  with (object) {
458    x = 1;
459  }
460  // Once for HasBinding
461  assertEquals(1, callCount);
462  assertEquals(1, object.x);
463  assertEquals('local', x);
464  with (object) {
465    x = 2;
466  }
467  // One more HasBinding.
468  assertEquals(2, callCount);
469  assertEquals(1, object.x);
470  assertEquals(2, x);
471}
472runTest(TestUnscopablesGetter);
473
474
475var global = this;
476function TestUnscopablesGetter2() {
477  var x = 'local';
478
479  var globalProto = Object.getPrototypeOf(global);
480  var protos = [{}, [], function() {}, global];
481  var objects = [{}, [], function() {}];
482
483  protos.forEach(function(proto) {
484    objects.forEach(function(object) {
485      Object.defineProperty(proto, 'x', {
486        get: function() {
487          assertEquals(object, this);
488          return 'proto';
489        },
490        configurable: true
491      });
492
493      object.__proto__ = proto;
494      Object.defineProperty(object, 'x', {
495        get: function() {
496          assertEquals(object, this);
497          return 'object';
498        },
499        configurable: true
500      });
501
502      with (object) {
503        assertEquals('object', x);
504      }
505
506      object[Symbol.unscopables] = {x: true};
507      with (object) {
508        assertEquals('local', x);
509      }
510
511      delete proto[Symbol.unscopables];
512      delete object[Symbol.unscopables];
513    });
514  });
515
516  delete global.x;
517  Object.setPrototypeOf(global, globalProto);
518}
519TestUnscopablesGetter2();
520
521
522function TestSetterOnBlacklisted(object, proto) {
523  var x = 'local';
524  Object.defineProperty(proto, 'x', {
525    set: function(x) {
526      assertUnreachable();
527    },
528    get: function() {
529      return 'proto';
530    },
531    configurable: true
532  });
533  Object.setPrototypeOf(object, proto);
534  Object.defineProperty(object, 'x', {
535    get: function() {
536      return this.x_;
537    },
538    set: function(x) {
539      this.x_ = x;
540    },
541    configurable: true
542  });
543  object.x_ = 1;
544
545  with (object) {
546    x = 2;
547    assertEquals(2, x);
548  }
549
550  assertEquals(2, object.x);
551
552  object[Symbol.unscopables] = {x: true};
553
554  with (object) {
555    x = 3;
556    assertEquals(3, x);
557  }
558
559  assertEquals(2, object.x);
560}
561runTest(TestSetterOnBlacklisted);
562
563
564function TestObjectsAsUnscopables(object, unscopables) {
565  var x = 1;
566  object.x = 2;
567
568  with (object) {
569    assertEquals(2, x);
570    object[Symbol.unscopables] = unscopables;
571    assertEquals(2, x);
572  }
573}
574runTest(TestObjectsAsUnscopables);
575
576
577function TestAccessorOnUnscopables(object) {
578  var x = 1;
579  object.x = 2;
580
581  var calls = 0;
582  var unscopables = {
583    get x() {
584      calls++;
585      return calls === 1 ? true : undefined;
586    }
587  };
588
589  with (object) {
590    assertEquals(2, x);
591    object[Symbol.unscopables] = unscopables;
592    assertEquals(1, x);
593    assertEquals(2, x);
594  }
595  assertEquals(2, calls);
596}
597runTest(TestAccessorOnUnscopables);
598
599
600function TestLengthUnscopables(object, proto) {
601  var length = 2;
602  with (object) {
603    assertEquals(1, length);
604    object[Symbol.unscopables] = {length: true};
605    assertEquals(2, length);
606    delete object[Symbol.unscopables];
607    assertEquals(1, length);
608  }
609}
610TestLengthUnscopables([1], Array.prototype);
611TestLengthUnscopables(function(x) {}, Function.prototype);
612TestLengthUnscopables(new String('x'), String.prototype);
613
614
615function TestFunctionNameUnscopables(object) {
616  var name = 'local';
617  with (object) {
618    assertEquals('f', name);
619    object[Symbol.unscopables] = {name: true};
620    assertEquals('local', name);
621    delete object[Symbol.unscopables];
622    assertEquals('f', name);
623  }
624}
625TestFunctionNameUnscopables(function f() {});
626
627
628function TestFunctionPrototypeUnscopables() {
629  var prototype = 'local';
630  var f = function() {};
631  var g = function() {};
632  Object.setPrototypeOf(f, g);
633  var fp = f.prototype;
634  var gp = g.prototype;
635  with (f) {
636    assertEquals(fp, prototype);
637    f[Symbol.unscopables] = {prototype: true};
638    assertEquals('local', prototype);
639    delete f[Symbol.unscopables];
640    assertEquals(fp, prototype);
641  }
642}
643TestFunctionPrototypeUnscopables(function() {});
644
645
646function TestFunctionArgumentsUnscopables() {
647  var func = function() {
648    var arguments = 'local';
649    var args = func.arguments;
650    with (func) {
651      assertEquals(args, arguments);
652      func[Symbol.unscopables] = {arguments: true};
653      assertEquals('local', arguments);
654      delete func[Symbol.unscopables];
655      assertEquals(args, arguments);
656    }
657  }
658  func(1);
659}
660TestFunctionArgumentsUnscopables();
661
662
663function TestArgumentsLengthUnscopables() {
664  var func = function() {
665    var length = 'local';
666    with (arguments) {
667      assertEquals(1, length);
668      arguments[Symbol.unscopables] = {length: true};
669      assertEquals('local', length);
670    }
671  }
672  func(1);
673}
674TestArgumentsLengthUnscopables();
675
676
677function TestFunctionCallerUnscopables() {
678  var func = function() {
679    var caller = 'local';
680    with (func) {
681      assertEquals(TestFunctionCallerUnscopables, caller);
682      func[Symbol.unscopables] = {caller: true};
683      assertEquals('local', caller);
684      delete func[Symbol.unscopables];
685      assertEquals(TestFunctionCallerUnscopables, caller);
686    }
687  }
688  func(1);
689}
690TestFunctionCallerUnscopables();
691
692
693function TestGetUnscopablesGetterThrows() {
694  var object = {
695    get x() {
696      assertUnreachable();
697    }
698  };
699  function CustomError() {}
700  Object.defineProperty(object, Symbol.unscopables, {
701    get: function() {
702      throw new CustomError();
703    }
704  });
705  assertThrows(function() {
706    with (object) {
707      x;
708    }
709  }, CustomError);
710}
711TestGetUnscopablesGetterThrows();
712
713
714function TestGetUnscopablesGetterThrows2() {
715  var object = {
716    get x() {
717      assertUnreachable();
718    }
719  };
720  function CustomError() {}
721
722  object[Symbol.unscopables] = {
723    get x() {
724      throw new CustomError();
725    }
726  };
727  assertThrows(function() {
728    with (object) {
729      x;
730    }
731  }, CustomError);
732}
733TestGetUnscopablesGetterThrows();
734