// 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();