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  object[Symbol.unscopables] = {x: true};
134  with (object) {
135    assertEquals(1, x);
136    assertEquals(5, y);
137    assertEquals(3, z);
138  }
139
140  object[Symbol.unscopables] = {x: 0, y: true};
141  with (object) {
142    assertEquals(1, x);
143    assertEquals(2, y);
144    assertEquals(3, z);
145  }
146}
147runTest(TestBasics);
148
149
150function TestUnscopableChain(object) {
151  var x = 1;
152  object.x = 2;
153
154  with (object) {
155    assertEquals(2, x);
156  }
157
158  object[Symbol.unscopables] = {
159    __proto__: {x: true}
160  };
161  with (object) {
162    assertEquals(1, x);
163  }
164}
165runTest(TestUnscopableChain);
166
167
168function TestBasicsSet(object) {
169  var x = 1;
170  object.x = 2;
171
172  with (object) {
173    assertEquals(2, x);
174  }
175
176  object[Symbol.unscopables] = {x: true};
177  with (object) {
178    assertEquals(1, x);
179    x = 3;
180    assertEquals(3, x);
181  }
182
183  assertEquals(3, x);
184  assertEquals(2, object.x);
185}
186runTest(TestBasicsSet);
187
188
189function TestOnProto(object, proto) {
190  var x = 1;
191  var y = 2;
192  var z = 3;
193  proto.x = 4;
194
195  Object.setPrototypeOf(object, proto);
196  object.y = 5;
197
198  with (object) {
199    assertEquals(4, x);
200    assertEquals(5, y);
201    assertEquals(3, z);
202  }
203
204  proto[Symbol.unscopables] = {x: true};
205  with (object) {
206    assertEquals(1, x);
207    assertEquals(5, y);
208    assertEquals(3, z);
209  }
210
211  object[Symbol.unscopables] = {y: true};
212  with (object) {
213    assertEquals(4, x);
214    assertEquals(2, y);
215    assertEquals(3, z);
216  }
217
218  proto[Symbol.unscopables] = {y: true};
219  object[Symbol.unscopables] = {x: true};
220  with (object) {
221    assertEquals(1, x);
222    assertEquals(5, y);
223    assertEquals(3, z);
224  }
225}
226runTest(TestOnProto);
227
228
229function TestSetBlockedOnProto(object, proto) {
230  var x = 1;
231  object.x = 2;
232
233  with (object) {
234    assertEquals(2, x);
235  }
236
237  Object.setPrototypeOf(object, proto);
238  proto[Symbol.unscopables] = {x: true};
239  with (object) {
240    assertEquals(1, x);
241    x = 3;
242    assertEquals(3, x);
243  }
244
245  assertEquals(3, x);
246  assertEquals(2, object.x);
247}
248runTest(TestSetBlockedOnProto);
249
250
251function TestNonObject(object) {
252  var x = 1;
253  var y = 2;
254  object.x = 3;
255  object.y = 4;
256
257  object[Symbol.unscopables] = 'xy';
258  with (object) {
259    assertEquals(3, x);
260    assertEquals(4, y);
261  }
262
263  object[Symbol.unscopables] = null;
264  with (object) {
265    assertEquals(3, x);
266    assertEquals(4, y);
267  }
268}
269runTest(TestNonObject);
270
271
272function TestChangeDuringWith(object) {
273  var x = 1;
274  var y = 2;
275  object.x = 3;
276  object.y = 4;
277
278  with (object) {
279    assertEquals(3, x);
280    assertEquals(4, y);
281    object[Symbol.unscopables] = {x: true};
282    assertEquals(1, x);
283    assertEquals(4, y);
284  }
285}
286runTest(TestChangeDuringWith);
287
288
289function TestChangeDuringWithWithPossibleOptimization(object) {
290  var x = 1;
291  object.x = 2;
292  with (object) {
293    for (var i = 0; i < 1000; i++) {
294      if (i === 500) object[Symbol.unscopables] = {x: true};
295      assertEquals(i < 500 ? 2: 1, x);
296    }
297  }
298}
299TestChangeDuringWithWithPossibleOptimization({});
300
301
302function TestChangeDuringWithWithPossibleOptimization2(object) {
303  var x = 1;
304  object.x = 2;
305  object[Symbol.unscopables] = {x: true};
306  with (object) {
307    for (var i = 0; i < 1000; i++) {
308      if (i === 500) delete object[Symbol.unscopables];
309      assertEquals(i < 500 ? 1 : 2, x);
310    }
311  }
312}
313TestChangeDuringWithWithPossibleOptimization2({});
314
315
316function TestChangeDuringWithWithPossibleOptimization3(object) {
317  var x = 1;
318  object.x = 2;
319  object[Symbol.unscopables] = {};
320  with (object) {
321    for (var i = 0; i < 1000; i++) {
322      if (i === 500) object[Symbol.unscopables].x = true;
323      assertEquals(i < 500 ? 2 : 1, x);
324    }
325  }
326}
327TestChangeDuringWithWithPossibleOptimization3({});
328
329
330function TestChangeDuringWithWithPossibleOptimization4(object) {
331  var x = 1;
332  object.x = 2;
333  object[Symbol.unscopables] = {x: true};
334  with (object) {
335    for (var i = 0; i < 1000; i++) {
336      if (i === 500) delete object[Symbol.unscopables].x;
337      assertEquals(i < 500 ? 1 : 2, x);
338    }
339  }
340}
341TestChangeDuringWithWithPossibleOptimization4({});
342
343
344function TestAccessorReceiver(object, proto) {
345  var x = 'local';
346
347  Object.defineProperty(proto, 'x', {
348    get: function() {
349      assertEquals(object, this);
350      return this.x_;
351    },
352    configurable: true
353  });
354  proto.x_ = 'proto';
355
356  Object.setPrototypeOf(object, proto);
357  proto.x_ = 'object';
358
359  with (object) {
360    assertEquals('object', x);
361  }
362}
363runTest(TestAccessorReceiver);
364
365
366function TestUnscopablesGetter(object) {
367  // This test gets really messy when object is the global since the assert
368  // functions are properties on the global object and the call count gets
369  // completely different.
370  if (object === global) return;
371
372  var x = 'local';
373  object.x = 'object';
374
375  var callCount = 0;
376  Object.defineProperty(object, Symbol.unscopables, {
377    get: function() {
378      callCount++;
379      return {};
380    },
381    configurable: true
382  });
383  with (object) {
384    assertEquals('object', x);
385  }
386  // Once for HasBinding
387  assertEquals(1, callCount);
388
389  callCount = 0;
390  Object.defineProperty(object, Symbol.unscopables, {
391    get: function() {
392      callCount++;
393      return {x: true};
394    },
395    configurable: true
396  });
397  with (object) {
398    assertEquals('local', x);
399  }
400  // Once for HasBinding
401  assertEquals(1, callCount);
402
403  callCount = 0;
404  Object.defineProperty(object, Symbol.unscopables, {
405    get: function() {
406      callCount++;
407      return callCount == 1 ? {} : {x: true};
408    },
409    configurable: true
410  });
411  with (object) {
412    x = 1;
413  }
414  // Once for HasBinding
415  assertEquals(1, callCount);
416  assertEquals(1, object.x);
417  assertEquals('local', x);
418  with (object) {
419    x = 2;
420  }
421  // One more HasBinding.
422  assertEquals(2, callCount);
423  assertEquals(1, object.x);
424  assertEquals(2, x);
425}
426runTest(TestUnscopablesGetter);
427
428
429var global = this;
430function TestUnscopablesGetter2() {
431  var x = 'local';
432
433  var globalProto = Object.getPrototypeOf(global);
434  var protos = [{}, [], function() {}, global];
435  var objects = [{}, [], function() {}];
436
437  protos.forEach(function(proto) {
438    objects.forEach(function(object) {
439      Object.defineProperty(proto, 'x', {
440        get: function() {
441          assertEquals(object, this);
442          return 'proto';
443        },
444        configurable: true
445      });
446
447      object.__proto__ = proto;
448      Object.defineProperty(object, 'x', {
449        get: function() {
450          assertEquals(object, this);
451          return 'object';
452        },
453        configurable: true
454      });
455
456      with (object) {
457        assertEquals('object', x);
458      }
459
460      object[Symbol.unscopables] = {x: true};
461      with (object) {
462        assertEquals('local', x);
463      }
464
465      delete proto[Symbol.unscopables];
466      delete object[Symbol.unscopables];
467    });
468  });
469
470  delete global.x;
471  Object.setPrototypeOf(global, globalProto);
472}
473TestUnscopablesGetter2();
474
475
476function TestSetterOnBlacklisted(object, proto) {
477  var x = 'local';
478  Object.defineProperty(proto, 'x', {
479    set: function(x) {
480      assertUnreachable();
481    },
482    get: function() {
483      return 'proto';
484    },
485    configurable: true
486  });
487  Object.setPrototypeOf(object, proto);
488  Object.defineProperty(object, 'x', {
489    get: function() {
490      return this.x_;
491    },
492    set: function(x) {
493      this.x_ = x;
494    },
495    configurable: true
496  });
497  object.x_ = 1;
498
499  with (object) {
500    x = 2;
501    assertEquals(2, x);
502  }
503
504  assertEquals(2, object.x);
505
506  object[Symbol.unscopables] = {x: true};
507
508  with (object) {
509    x = 3;
510    assertEquals(3, x);
511  }
512
513  assertEquals(2, object.x);
514}
515runTest(TestSetterOnBlacklisted);
516
517
518function TestObjectsAsUnscopables(object, unscopables) {
519  var x = 1;
520  object.x = 2;
521
522  with (object) {
523    assertEquals(2, x);
524    object[Symbol.unscopables] = unscopables;
525    assertEquals(2, x);
526  }
527}
528runTest(TestObjectsAsUnscopables);
529
530
531function TestAccessorOnUnscopables(object) {
532  var x = 1;
533  object.x = 2;
534
535  var unscopables = {
536    get x() {
537      assertUnreachable();
538    }
539  };
540
541  with (object) {
542    assertEquals(2, x);
543    object[Symbol.unscopables] = unscopables;
544    assertEquals(1, x);
545  }
546}
547runTest(TestAccessorOnUnscopables);
548
549
550function TestLengthUnscopables(object, proto) {
551  var length = 2;
552  with (object) {
553    assertEquals(1, length);
554    object[Symbol.unscopables] = {length: true};
555    assertEquals(2, length);
556    delete object[Symbol.unscopables];
557    assertEquals(1, length);
558  }
559}
560TestLengthUnscopables([1], Array.prototype);
561TestLengthUnscopables(function(x) {}, Function.prototype);
562TestLengthUnscopables(new String('x'), String.prototype);
563
564
565function TestFunctionNameUnscopables(object) {
566  var name = 'local';
567  with (object) {
568    assertEquals('f', name);
569    object[Symbol.unscopables] = {name: true};
570    assertEquals('local', name);
571    delete object[Symbol.unscopables];
572    assertEquals('f', name);
573  }
574}
575TestFunctionNameUnscopables(function f() {});
576
577
578function TestFunctionPrototypeUnscopables() {
579  var prototype = 'local';
580  var f = function() {};
581  var g = function() {};
582  Object.setPrototypeOf(f, g);
583  var fp = f.prototype;
584  var gp = g.prototype;
585  with (f) {
586    assertEquals(fp, prototype);
587    f[Symbol.unscopables] = {prototype: true};
588    assertEquals('local', prototype);
589    delete f[Symbol.unscopables];
590    assertEquals(fp, prototype);
591  }
592}
593TestFunctionPrototypeUnscopables(function() {});
594
595
596function TestFunctionArgumentsUnscopables() {
597  var func = function() {
598    var arguments = 'local';
599    var args = func.arguments;
600    with (func) {
601      assertEquals(args, arguments);
602      func[Symbol.unscopables] = {arguments: true};
603      assertEquals('local', arguments);
604      delete func[Symbol.unscopables];
605      assertEquals(args, arguments);
606    }
607  }
608  func(1);
609}
610TestFunctionArgumentsUnscopables();
611
612
613function TestArgumentsLengthUnscopables() {
614  var func = function() {
615    var length = 'local';
616    with (arguments) {
617      assertEquals(1, length);
618      arguments[Symbol.unscopables] = {length: true};
619      assertEquals('local', length);
620    }
621  }
622  func(1);
623}
624TestArgumentsLengthUnscopables();
625
626
627function TestFunctionCallerUnscopables() {
628  var func = function() {
629    var caller = 'local';
630    with (func) {
631      assertEquals(TestFunctionCallerUnscopables, caller);
632      func[Symbol.unscopables] = {caller: true};
633      assertEquals('local', caller);
634      delete func[Symbol.unscopables];
635      assertEquals(TestFunctionCallerUnscopables, caller);
636    }
637  }
638  func(1);
639}
640TestFunctionCallerUnscopables();
641
642
643function TestGetUnscopablesGetterThrows() {
644  var object = {
645    get x() {
646      assertUnreachable();
647    }
648  };
649  function CustomError() {}
650  Object.defineProperty(object, Symbol.unscopables, {
651    get: function() {
652      throw new CustomError();
653    }
654  });
655  assertThrows(function() {
656    with (object) {
657      x;
658    }
659  }, CustomError);
660}
661TestGetUnscopablesGetterThrows();
662