// Copyright 2014 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. var global = this; var globalProto = Object.getPrototypeOf(global); // Number of objects being tested. There is an assert ensuring this is correct. var objectCount = 21; function runTest(f) { function restore(object, oldProto) { delete object[Symbol.unscopables]; delete object.x; delete object.x_; delete object.y; delete object.z; Object.setPrototypeOf(object, oldProto); } function getObject(i) { var objects = [ {}, [], function() {}, function() { return arguments; }(), function() { 'use strict'; return arguments; }(), Object(1), Object(true), Object('bla'), new Date, new RegExp, new Set, new Map, new WeakMap, new WeakSet, new ArrayBuffer(10), new Int32Array(5), Object, Function, Date, RegExp, global ]; assertEquals(objectCount, objects.length); return objects[i]; } // Tests depends on this not being there to start with. delete Array.prototype[Symbol.unscopables]; if (f.length === 1) { for (var i = 0; i < objectCount; i++) { var object = getObject(i); var oldObjectProto = Object.getPrototypeOf(object); f(object); restore(object, oldObjectProto); } } else { for (var i = 0; i < objectCount; i++) { for (var j = 0; j < objectCount; j++) { var object = getObject(i); var proto = getObject(j); if (object === proto) { continue; } var oldObjectProto = Object.getPrototypeOf(object); var oldProtoProto = Object.getPrototypeOf(proto); f(object, proto); restore(object, oldObjectProto); restore(proto, oldProtoProto); } } } } // Test array first, since other tests are changing // Array.prototype[Symbol.unscopables]. function TestArrayPrototypeUnscopables() { var descr = Object.getOwnPropertyDescriptor(Array.prototype, Symbol.unscopables); assertFalse(descr.enumerable); assertFalse(descr.writable); assertTrue(descr.configurable); assertEquals(null, Object.getPrototypeOf(descr.value)); var copyWithin = 'local copyWithin'; var entries = 'local entries'; var fill = 'local fill'; var find = 'local find'; var findIndex = 'local findIndex'; var keys = 'local keys'; var values = 'local values'; var array = []; array.toString = 42; with (array) { assertEquals('local copyWithin', copyWithin); assertEquals('local entries', entries); assertEquals('local fill', fill); assertEquals('local find', find); assertEquals('local findIndex', findIndex); assertEquals('local keys', keys); assertEquals('local values', values); assertEquals(42, toString); } } TestArrayPrototypeUnscopables(); function TestBasics(object) { var x = 1; var y = 2; var z = 3; object.x = 4; object.y = 5; with (object) { assertEquals(4, x); assertEquals(5, y); assertEquals(3, z); } var truthyValues = [true, 1, 'x', {}, Symbol()]; for (var truthyValue of truthyValues) { object[Symbol.unscopables] = {x: truthyValue}; with (object) { assertEquals(1, x); assertEquals(5, y); assertEquals(3, z); } } var falsyValues = [false, 0, -0, NaN, '', null, undefined]; for (var falsyValue of falsyValues) { object[Symbol.unscopables] = {x: falsyValue, y: true}; with (object) { assertEquals(4, x); assertEquals(2, y); assertEquals(3, z); } } for (var xFalsy of falsyValues) { for (var yFalsy of falsyValues) { object[Symbol.unscopables] = {x: xFalsy, y: yFalsy}; with (object) { assertEquals(4, x); assertEquals(5, y); assertEquals(3, z); } } } } runTest(TestBasics); function TestUnscopableChain(object) { var x = 1; object.x = 2; with (object) { assertEquals(2, x); } object[Symbol.unscopables] = { __proto__: {x: true} }; with (object) { assertEquals(1, x); } object[Symbol.unscopables] = { __proto__: {x: undefined} }; with (object) { assertEquals(2, x); } } runTest(TestUnscopableChain); function TestBasicsSet(object) { var x = 1; object.x = 2; with (object) { assertEquals(2, x); } object[Symbol.unscopables] = {x: true}; with (object) { assertEquals(1, x); x = 3; assertEquals(3, x); } assertEquals(3, x); assertEquals(2, object.x); } runTest(TestBasicsSet); function TestOnProto(object, proto) { var x = 1; var y = 2; var z = 3; proto.x = 4; Object.setPrototypeOf(object, proto); object.y = 5; with (object) { assertEquals(4, x); assertEquals(5, y); assertEquals(3, z); } proto[Symbol.unscopables] = {x: true}; with (object) { assertEquals(1, x); assertEquals(5, y); assertEquals(3, z); } object[Symbol.unscopables] = {y: true}; with (object) { assertEquals(4, x); assertEquals(2, y); assertEquals(3, z); } proto[Symbol.unscopables] = {y: true}; object[Symbol.unscopables] = {x: true}; with (object) { assertEquals(1, x); assertEquals(5, y); assertEquals(3, z); } proto[Symbol.unscopables] = {y: true}; object[Symbol.unscopables] = {x: true, y: undefined}; with (object) { assertEquals(1, x); assertEquals(5, y); assertEquals(3, z); } } runTest(TestOnProto); function TestSetBlockedOnProto(object, proto) { var x = 1; object.x = 2; with (object) { assertEquals(2, x); } Object.setPrototypeOf(object, proto); proto[Symbol.unscopables] = {x: true}; with (object) { assertEquals(1, x); x = 3; assertEquals(3, x); } assertEquals(3, x); assertEquals(2, object.x); } runTest(TestSetBlockedOnProto); function TestNonObject(object) { var x = 1; var y = 2; object.x = 3; object.y = 4; object[Symbol.unscopables] = 'xy'; with (object) { assertEquals(3, x); assertEquals(4, y); } object[Symbol.unscopables] = null; with (object) { assertEquals(3, x); assertEquals(4, y); } } runTest(TestNonObject); function TestChangeDuringWith(object) { var x = 1; var y = 2; object.x = 3; object.y = 4; with (object) { assertEquals(3, x); assertEquals(4, y); object[Symbol.unscopables] = {x: true}; assertEquals(1, x); assertEquals(4, y); } } runTest(TestChangeDuringWith); function TestChangeDuringWithWithPossibleOptimization(object) { var x = 1; object.x = 2; with (object) { for (var i = 0; i < 1000; i++) { if (i === 500) object[Symbol.unscopables] = {x: true}; assertEquals(i < 500 ? 2: 1, x); } } } TestChangeDuringWithWithPossibleOptimization({}); function TestChangeDuringWithWithPossibleOptimization2(object) { var x = 1; object.x = 2; object[Symbol.unscopables] = {x: true}; with (object) { for (var i = 0; i < 1000; i++) { if (i === 500) delete object[Symbol.unscopables]; assertEquals(i < 500 ? 1 : 2, x); } } } TestChangeDuringWithWithPossibleOptimization2({}); function TestChangeDuringWithWithPossibleOptimization3(object) { var x = 1; object.x = 2; object[Symbol.unscopables] = {}; with (object) { for (var i = 0; i < 1000; i++) { if (i === 500) object[Symbol.unscopables].x = true; assertEquals(i < 500 ? 2 : 1, x); } } } TestChangeDuringWithWithPossibleOptimization3({}); function TestChangeDuringWithWithPossibleOptimization4(object) { var x = 1; object.x = 2; object[Symbol.unscopables] = {x: true}; with (object) { for (var i = 0; i < 1000; i++) { if (i === 500) delete object[Symbol.unscopables].x; assertEquals(i < 500 ? 1 : 2, x); } } } TestChangeDuringWithWithPossibleOptimization4({}); function TestChangeDuringWithWithPossibleOptimization4(object) { var x = 1; object.x = 2; object[Symbol.unscopables] = {x: true}; with (object) { for (var i = 0; i < 1000; i++) { if (i === 500) object[Symbol.unscopables].x = undefined; assertEquals(i < 500 ? 1 : 2, x); } } } TestChangeDuringWithWithPossibleOptimization4({}); function TestAccessorReceiver(object, proto) { var x = 'local'; Object.defineProperty(proto, 'x', { get: function() { assertEquals(object, this); return this.x_; }, configurable: true }); proto.x_ = 'proto'; Object.setPrototypeOf(object, proto); proto.x_ = 'object'; with (object) { assertEquals('object', x); } } runTest(TestAccessorReceiver); function TestUnscopablesGetter(object) { // This test gets really messy when object is the global since the assert // functions are properties on the global object and the call count gets // completely different. if (object === global) return; var x = 'local'; object.x = 'object'; var callCount = 0; Object.defineProperty(object, Symbol.unscopables, { get: function() { callCount++; return {}; }, configurable: true }); with (object) { assertEquals('object', x); } // Once for HasBinding assertEquals(1, callCount); callCount = 0; Object.defineProperty(object, Symbol.unscopables, { get: function() { callCount++; return {x: true}; }, configurable: true }); with (object) { assertEquals('local', x); } // Once for HasBinding assertEquals(1, callCount); callCount = 0; Object.defineProperty(object, Symbol.unscopables, { get: function() { callCount++; return callCount == 1 ? {} : {x: true}; }, configurable: true }); with (object) { x = 1; } // Once for HasBinding assertEquals(1, callCount); assertEquals(1, object.x); assertEquals('local', x); with (object) { x = 2; } // One more HasBinding. assertEquals(2, callCount); assertEquals(1, object.x); assertEquals(2, x); } runTest(TestUnscopablesGetter); var global = this; function TestUnscopablesGetter2() { var x = 'local'; var globalProto = Object.getPrototypeOf(global); var protos = [{}, [], function() {}, global]; var objects = [{}, [], function() {}]; protos.forEach(function(proto) { objects.forEach(function(object) { Object.defineProperty(proto, 'x', { get: function() { assertEquals(object, this); return 'proto'; }, configurable: true }); object.__proto__ = proto; Object.defineProperty(object, 'x', { get: function() { assertEquals(object, this); return 'object'; }, configurable: true }); with (object) { assertEquals('object', x); } object[Symbol.unscopables] = {x: true}; with (object) { assertEquals('local', x); } delete proto[Symbol.unscopables]; delete object[Symbol.unscopables]; }); }); delete global.x; Object.setPrototypeOf(global, globalProto); } TestUnscopablesGetter2(); function TestSetterOnBlacklisted(object, proto) { var x = 'local'; Object.defineProperty(proto, 'x', { set: function(x) { assertUnreachable(); }, get: function() { return 'proto'; }, configurable: true }); Object.setPrototypeOf(object, proto); Object.defineProperty(object, 'x', { get: function() { return this.x_; }, set: function(x) { this.x_ = x; }, configurable: true }); object.x_ = 1; with (object) { x = 2; assertEquals(2, x); } assertEquals(2, object.x); object[Symbol.unscopables] = {x: true}; with (object) { x = 3; assertEquals(3, x); } assertEquals(2, object.x); } runTest(TestSetterOnBlacklisted); function TestObjectsAsUnscopables(object, unscopables) { var x = 1; object.x = 2; with (object) { assertEquals(2, x); object[Symbol.unscopables] = unscopables; assertEquals(2, x); } } runTest(TestObjectsAsUnscopables); function TestAccessorOnUnscopables(object) { var x = 1; object.x = 2; var calls = 0; var unscopables = { get x() { calls++; return calls === 1 ? true : undefined; } }; with (object) { assertEquals(2, x); object[Symbol.unscopables] = unscopables; assertEquals(1, x); assertEquals(2, x); } assertEquals(2, calls); } runTest(TestAccessorOnUnscopables); function TestLengthUnscopables(object, proto) { var length = 2; with (object) { assertEquals(1, length); object[Symbol.unscopables] = {length: true}; assertEquals(2, length); delete object[Symbol.unscopables]; assertEquals(1, length); } } TestLengthUnscopables([1], Array.prototype); TestLengthUnscopables(function(x) {}, Function.prototype); TestLengthUnscopables(new String('x'), String.prototype); function TestFunctionNameUnscopables(object) { var name = 'local'; with (object) { assertEquals('f', name); object[Symbol.unscopables] = {name: true}; assertEquals('local', name); delete object[Symbol.unscopables]; assertEquals('f', name); } } TestFunctionNameUnscopables(function f() {}); function TestFunctionPrototypeUnscopables() { var prototype = 'local'; var f = function() {}; var g = function() {}; Object.setPrototypeOf(f, g); var fp = f.prototype; var gp = g.prototype; with (f) { assertEquals(fp, prototype); f[Symbol.unscopables] = {prototype: true}; assertEquals('local', prototype); delete f[Symbol.unscopables]; assertEquals(fp, prototype); } } TestFunctionPrototypeUnscopables(function() {}); function TestFunctionArgumentsUnscopables() { var func = function() { var arguments = 'local'; var args = func.arguments; with (func) { assertEquals(args, arguments); func[Symbol.unscopables] = {arguments: true}; assertEquals('local', arguments); delete func[Symbol.unscopables]; assertEquals(args, arguments); } } func(1); } TestFunctionArgumentsUnscopables(); function TestArgumentsLengthUnscopables() { var func = function() { var length = 'local'; with (arguments) { assertEquals(1, length); arguments[Symbol.unscopables] = {length: true}; assertEquals('local', length); } } func(1); } TestArgumentsLengthUnscopables(); function TestFunctionCallerUnscopables() { var func = function() { var caller = 'local'; with (func) { assertEquals(TestFunctionCallerUnscopables, caller); func[Symbol.unscopables] = {caller: true}; assertEquals('local', caller); delete func[Symbol.unscopables]; assertEquals(TestFunctionCallerUnscopables, caller); } } func(1); } TestFunctionCallerUnscopables(); function TestGetUnscopablesGetterThrows() { var object = { get x() { assertUnreachable(); } }; function CustomError() {} Object.defineProperty(object, Symbol.unscopables, { get: function() { throw new CustomError(); } }); assertThrows(function() { with (object) { x; } }, CustomError); } TestGetUnscopablesGetterThrows(); function TestGetUnscopablesGetterThrows2() { var object = { get x() { assertUnreachable(); } }; function CustomError() {} object[Symbol.unscopables] = { get x() { throw new CustomError(); } }; assertThrows(function() { with (object) { x; } }, CustomError); } TestGetUnscopablesGetterThrows();