blob: 40424c00dcdf8c9aa6fb1cc246e4778b74b8343d [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
// ES6 restricted property tests
WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
function verifyAttributes(obj, prop, attribs, name) {
var p = Object.getOwnPropertyDescriptor(obj, prop);
assert.areNotEqual(undefined, p, name + " does not have property named " + prop);
assert.areEqual(attribs.writable, p.writable, name + " has property named " + prop + " with writable = " + attribs.writable);
assert.areEqual(attribs.enumerable, p.enumerable, name + " has property named " + prop + " with enumerable = " + attribs.enumerable);
assert.areEqual(attribs.configurable, p.configurable, name + " has property named " + prop + " with configurable = " + attribs.configurable);
}
function verifyHasRestrictedOwnProperties(obj, name) {
assert.isTrue(obj.hasOwnProperty('caller'), name + " reports that it has own property 'caller'")
assert.isTrue(obj.hasOwnProperty('arguments'), name + " reports that it has own property 'arguments'")
var names = Object.getOwnPropertyNames(obj);
assert.areNotEqual(-1, names.findIndex((e) => { return e === 'arguments'; }), name + " has 'arguments' own property");
assert.areNotEqual(-1, names.findIndex((e) => { return e === 'caller'; }), name + " has 'caller' own property");
verifyAttributes(obj, 'caller', { writable: false, enumerable: false, configurable: false }, name);
assert.isFalse(obj.propertyIsEnumerable('caller'), name + " says 'caller' property is not enumerable");
verifyAttributes(obj, 'arguments', { writable: false, enumerable: false, configurable: false }, name);
assert.isFalse(obj.propertyIsEnumerable('arguments'), name + " says 'arguments' property is not enumerable");
assert.areEqual(null, obj.caller, name + " says 'caller' property is null")
assert.areEqual(null, obj.arguments, name + " says 'arguments' property is null")
assert.doesNotThrow(function() { obj.caller = 'something'; }, name + " has 'caller' property which can't be assigned to");
assert.doesNotThrow(function() { obj.arguments = 'something'; }, name + " has 'arguments' property which can't be assigned to");
assert.throws(function() { 'use strict'; obj.caller = 'something'; }, TypeError, name + " has 'caller' own property but it is not configurable so we will throw in strict mode", "Assignment to read-only properties is not allowed in strict mode");
assert.throws(function() { 'use strict'; obj.arguments = 'something'; }, TypeError, name + " has 'arguments' own property but it is not configurable so we will throw in strict mode", "Assignment to read-only properties is not allowed in strict mode");
assert.areEqual(null, obj.caller, name + " says 'caller' property is null")
assert.areEqual(null, obj.arguments, name + " says 'arguments' property is null")
assert.throws(function() { Object.defineProperty(obj, 'arguments', { value: 123 }); }, TypeError, name + " has 'arguments' property as non-writable, non-configurable", "Cannot modify non-writable property 'arguments'");
assert.throws(function() { Object.defineProperty(obj, 'caller', { value: 123 }); }, TypeError, name + " has 'caller' property as non-writable, non-configurable", "Cannot modify non-writable property 'caller'");
assert.isFalse(delete obj.arguments, name + " has 'arguments' property as non-configurable so delete returns false");
assert.isFalse(delete obj.caller, name + " has 'caller' property as non-configurable so delete returns false");
assert.throws(function() { 'use strict'; delete obj.caller; }, TypeError, name + " has 'caller' own property but it is not configurable so we will throw in strict mode", "Calling delete on 'caller' is not allowed in strict mode");
assert.throws(function() { 'use strict'; delete obj.arguments; }, TypeError, name + " has 'arguments' own property but it is not configurable so we will throw in strict mode", "Calling delete on 'arguments' is not allowed in strict mode");
}
function verifyDoesNotHaveRestrictedOwnProperties(obj, name) {
assert.isFalse(obj.hasOwnProperty('caller'), name + " does not report that it has own property 'caller'")
assert.isFalse(obj.hasOwnProperty('arguments'), name + " does not report that it has own property 'arguments'")
var names = Object.getOwnPropertyNames(obj);
assert.areEqual(-1, names.findIndex((e) => { return e === 'arguments'; }), name + " does not have 'arguments' own property");
assert.areEqual(-1, names.findIndex((e) => { return e === 'caller'; }), name + " does not have 'caller' own property");
assert.areEqual(undefined, Object.getOwnPropertyDescriptor(obj, 'caller'), name + " does not have 'caller' own property")
assert.isFalse(obj.propertyIsEnumerable('caller'), name + " says 'caller' property is not enumerable");
assert.areEqual(undefined, Object.getOwnPropertyDescriptor(obj, 'arguments'), name + " does not have 'arguments' own property");
assert.isFalse(obj.propertyIsEnumerable('arguments'), name + " says 'arguments' property is not enumerable");
assert.throws(function() { obj.caller; }, TypeError, name + " throws on access to 'caller' property", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
assert.throws(function() { obj.arguments; }, TypeError, name + " throws on access to 'arguments' property", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
assert.throws(function() { 'use strict'; obj.caller; }, TypeError, name + " throws on access to 'caller' property in strict mode", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
assert.throws(function() { 'use strict'; obj.arguments; }, TypeError, name + " throws on access to 'arguments' property in strict mode", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
assert.throws(function() { obj.caller = 'something'; }, TypeError, name + " throws trying to assign to 'caller' property", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
assert.throws(function() { obj.arguments = 'something'; }, TypeError, name + " throws trying to assign to 'arguments' property", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
assert.throws(function() { 'use strict'; obj.caller = 'something'; }, TypeError, name + " throws trying to assign to 'caller' property in strict mode", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
assert.throws(function() { 'use strict'; obj.arguments = 'something'; }, TypeError, name + " throws trying to assign to 'arguments' property in strict mode", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
assert.isTrue(delete obj.arguments, name + " allows deleting own property named 'arguments' if that property doesn't exist");
assert.doesNotThrow(function() { Object.defineProperty(obj, 'arguments', { value: 123, writable: true, enumerable: true, configurable: true }); }, name + " doesn't have own 'arguments' property");
assert.isTrue(obj.hasOwnProperty('arguments'), name + " has own property 'arguments' after defineProperty")
assert.isTrue(obj.propertyIsEnumerable('arguments'), name + " says 'arguments' property is enumerable if it is an enumerable own property");
assert.areEqual(123, obj.arguments, name + " can have an own property defined for 'arguments'")
verifyAttributes(obj, 'arguments', { writable: true, enumerable: true, configurable: true }, name);
assert.isTrue(delete obj.arguments, name + " allows deleting own property named 'arguments' if that property does exist");
assert.isFalse(obj.hasOwnProperty('arguments'), name + " doesn't have own property 'arguments' after delete")
assert.isTrue(delete obj.caller, name + " allows deleting own property named 'caller' if that property doesn't exist");
assert.doesNotThrow(function() { Object.defineProperty(obj, 'caller', { value: 123, writable: true, enumerable: true, configurable: true }); }, name + " doesn't have own 'caller' property");
assert.isTrue(obj.hasOwnProperty('caller'), name + " has own property 'caller' after defineProperty")
assert.isTrue(obj.propertyIsEnumerable('caller'), name + " says 'caller' property is enumerable if it is an enumerable own property");
assert.areEqual(123, obj.caller, name + " can have an own property defined for 'caller'")
verifyAttributes(obj, 'caller', { writable: true, enumerable: true, configurable: true }, name);
assert.isTrue(delete obj.caller, name + " allows deleting own property named 'caller' if that property does exist");
assert.isFalse(obj.hasOwnProperty('caller'), name + " doesn't have own property 'caller' after delete")
// Remove Function.prototype from the prototype chain.
Object.setPrototypeOf(obj, Object.prototype);
assert.areEqual(undefined, obj.arguments, name + " does not initially have 'arguments' property when disconnected from Function.prototype");
assert.doesNotThrow(function() { obj.arguments = 'abc'; }, name + " can set the 'arguments' property when disconnected from Function.prototype");
assert.areEqual('abc', obj.arguments, name + " can set the 'arguments' property when disconnected from Function.prototype");
assert.isTrue(obj.hasOwnProperty('arguments'), name + " has 'arguments' own property")
assert.isTrue(obj.propertyIsEnumerable('arguments'), name + " says 'arguments' property is enumerable if it is an enumerable own property");
verifyAttributes(obj, 'arguments', { writable: true, enumerable: true, configurable: true }, name);
assert.isTrue(delete obj.arguments, name + " allows deleting own property named 'arguments' if that property does exist");
assert.isFalse(obj.hasOwnProperty('arguments'), name + " doesn't have own property 'arguments' after delete")
assert.areEqual(undefined, obj.caller, name + " does not initially have 'caller' property when disconnected from Function.prototype");
assert.doesNotThrow(function() { obj.caller = 'abc'; }, name + " can set the 'caller' property when disconnected from Function.prototype");
assert.areEqual('abc', obj.caller, name + " can set the 'caller' property when disconnected from Function.prototype");
assert.isTrue(obj.hasOwnProperty('caller'), name + " has 'caller' own property")
assert.isTrue(obj.propertyIsEnumerable('caller'), name + " says 'caller' property is enumerable if it is an enumerable own property");
verifyAttributes(obj, 'caller', { writable: true, enumerable: true, configurable: true }, name);
assert.isTrue(delete obj.caller, name + " allows deleting own property named 'caller' if that property does exist");
assert.isFalse(obj.hasOwnProperty('caller'), name + " doesn't have own property 'caller' after delete")
}
var tests = [
{
name: "Restricted properties of Function.prototype",
body: function () {
var obj = Function.prototype;
assert.isTrue(obj.hasOwnProperty('caller'), "Function.prototype has own property 'caller'")
assert.isTrue(obj.hasOwnProperty('arguments'), "Function.prototype has own property 'arguments'")
var p = Object.getOwnPropertyDescriptor(obj, 'caller');
assert.isFalse(p.enumerable, "Function.prototype function has 'caller' own property which is not enumerable");
assert.isFalse(p.configurable, "Function.prototype function has 'caller' own property which is not configurable");
assert.isFalse(obj.propertyIsEnumerable('caller'), "Function.prototype says 'caller' property is not enumerable");
assert.areEqual('function', typeof p.get, "Function.prototype['caller'] has get accessor function");
assert.areEqual('function', typeof p.set, "Function.prototype['caller'] has set accessor function");
assert.throws(function() { p.get(); }, TypeError, "Function.prototype['caller'] has get accessor which throws");
assert.throws(function() { p.set(); }, TypeError, "Function.prototype['caller'] has set accessor which throws");
assert.isTrue(p.get === p.set, "Function.prototype returns the same ThrowTypeError function for get/set accessor of 'caller' property");
var p2 = Object.getOwnPropertyDescriptor(obj, 'arguments');
assert.isFalse(p2.enumerable, "Function.prototype function has 'arguments' own property which is not enumerable");
assert.isFalse(p2.configurable, "Function.prototype function has 'arguments' own property which is not configurable");
assert.isFalse(obj.propertyIsEnumerable('arguments'), "Function.prototype says 'arguments' property is not enumerable");
assert.areEqual('function', typeof p2.get, "Function.prototype['arguments'] has get accessor function");
assert.areEqual('function', typeof p2.set, "Function.prototype['arguments'] has set accessor function");
assert.throws(function() { p2.get(); }, TypeError, "Function.prototype['arguments'] has get accessor which throws");
assert.throws(function() { p2.set(); }, TypeError, "Function.prototype['arguments'] has set accessor which throws");
assert.isTrue(p2.get === p2.set, "Function.prototype returns the same ThrowTypeError function for get/set accessor of 'arguments' property");
assert.isTrue(p.get === p2.get, "Function.prototype returns the same ThrowTypeError function for accessor of both 'arguments' and 'caller' properties");
var names = Object.getOwnPropertyNames(obj);
assert.areNotEqual(-1, names.findIndex((e) => { return e === 'arguments'; }), "Function.prototype has 'arguments' own property");
assert.areNotEqual(-1, names.findIndex((e) => { return e === 'caller'; }), "Function.prototype has 'caller' own property");
assert.throws(function() { obj.caller; }, TypeError, "Function.prototype throws on access to 'caller' property", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
assert.throws(function() { obj.arguments; }, TypeError, "Function.prototype throws on access to 'arguments' property", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
assert.throws(function() { obj.caller = 'something'; }, TypeError, "Function.prototype throws trying to assign to 'caller' property", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
assert.throws(function() { obj.arguments = 'something'; }, TypeError, "Function.prototype throws trying to assign to 'arguments' property", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
// TODO: These descriptors should have configurable set to true so remaining asserts in this test should actually succeed
assert.throws(function() { Object.defineProperty(obj, 'arguments', { value: 123 }); }, TypeError, "Function.prototype has 'arguments' property as non-configurable", "Cannot redefine non-configurable property 'arguments'");
assert.throws(function() { Object.defineProperty(obj, 'caller', { value: 123 }); }, TypeError, "Function.prototype has 'caller' property as non-configurable", "Cannot redefine non-configurable property 'caller'");
assert.isFalse(delete obj.arguments, "Function.prototype has 'arguments' property as non-configurable so delete returns false");
assert.isFalse(delete obj.caller, "Function.prototype has 'caller' property as non-configurable so delete returns false");
assert.throws(function() { 'use strict'; delete obj.caller; }, TypeError, "Function.prototype has 'caller' own property but it is not configurable so we will throw in strict mode", "Calling delete on 'caller' is not allowed in strict mode");
assert.throws(function() { 'use strict'; delete obj.arguments; }, TypeError, "Function.prototype has 'arguments' own property but it is not configurable so we will throw in strict mode", "Calling delete on 'arguments' is not allowed in strict mode");
}
},
{
name: "Restricted properties of non-strict function",
body: function () {
function obj() {};
verifyHasRestrictedOwnProperties(obj, "Non-strict function");
}
},
{
name: "Restricted properties of strict function",
body: function () {
function foo() { 'use strict'; };
verifyDoesNotHaveRestrictedOwnProperties(foo, "Strict function");
}
},
{
name: "Restricted properties of class",
body: function () {
class A { };
verifyDoesNotHaveRestrictedOwnProperties(A, "Class");
}
},
{
name: "Restricted properties of class static method",
body: function () {
class A {
static static_method() { }
};
verifyDoesNotHaveRestrictedOwnProperties(A.static_method, "Class static method");
}
},
{
name: "Restricted properties of strict-mode class static method",
body: function () {
class A {
static static_method() { 'use strict'; }
};
verifyDoesNotHaveRestrictedOwnProperties(A.static_method, "Class strict-mode static method");
}
},
{
name: "Restricted properties of class method",
body: function () {
class A {
method() { }
};
verifyDoesNotHaveRestrictedOwnProperties(A.prototype.method, "Class method");
}
},
{
name: "Restricted properties of strict-mode class method",
body: function () {
class A {
method() { 'use strict'; }
};
verifyDoesNotHaveRestrictedOwnProperties(A.prototype.method, "Class strict-mode method");
}
},
{
name: "Restricted properties of class with 'caller' static method",
body: function () {
var obj = class A {
static caller() { return 42; }
};
assert.isTrue(obj.hasOwnProperty('caller'), "Class does has own property 'caller'")
assert.isFalse(obj.hasOwnProperty('arguments'), "Class does not report that it has own property 'arguments'")
assert.areEqual('{"writable":true,"enumerable":false,"configurable":true}', JSON.stringify(Object.getOwnPropertyDescriptor(obj, 'caller')), "Class does not have 'caller' own property")
assert.areEqual(undefined, JSON.stringify(Object.getOwnPropertyDescriptor(obj, 'arguments')), "Class does not have 'arguments' own property");
assert.areEqual('["caller","length","name","prototype"]', JSON.stringify(Object.getOwnPropertyNames(obj).sort()), "Class does not have 'caller' and 'arguments' own properties");
assert.areEqual(42, obj.caller(), "Accessing the 'caller' property is not restricted");
assert.throws(function() { obj.arguments; }, TypeError, "Class throws on access to 'arguments' property", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
}
},
{
name: "Restricted properties of class with 'arguments' static get method",
body: function () {
var obj = class A {
static get arguments() { return 42; }
};
assert.isFalse(obj.hasOwnProperty('caller'), "Class does not report that it has own property 'caller'")
assert.isTrue(obj.hasOwnProperty('arguments'), "Class has own property 'arguments'")
assert.areEqual(undefined, JSON.stringify(Object.getOwnPropertyDescriptor(obj, 'caller')), "Class does not have 'caller' own property")
assert.areEqual('{"enumerable":false,"configurable":true}', JSON.stringify(Object.getOwnPropertyDescriptor(obj, 'arguments')), "Class has 'arguments' own property");
assert.areEqual('["arguments","length","name","prototype"]', JSON.stringify(Object.getOwnPropertyNames(obj).sort()), "Class has 'arguments' own property, no 'caller' own property");
assert.throws(function() { obj.caller; }, TypeError, "Class method throws on access to 'caller' property", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
assert.areEqual(42, obj.arguments, "Accessing the 'arguments' property is not restricted");
}
},
{
name: "Restricted properties of class with 'arguments' set method",
body: function () {
var my_v;
class A {
set arguments(v) { my_v = v; }
};
var obj = A;
assert.isFalse(obj.hasOwnProperty('caller'), "Class does not report that it has own property 'caller'")
assert.isFalse(obj.hasOwnProperty('arguments'), "Class does not report that it has own property 'arguments'")
assert.areEqual(undefined, JSON.stringify(Object.getOwnPropertyDescriptor(obj, 'caller')), "Class does not have 'caller' own property")
assert.areEqual(undefined, JSON.stringify(Object.getOwnPropertyDescriptor(obj, 'arguments')), "Class has 'arguments' own property");
assert.areEqual('["length","name","prototype"]', JSON.stringify(Object.getOwnPropertyNames(obj).sort()), "Class has 'arguments' own property, no 'caller' own property");
assert.throws(function() { obj.caller; }, TypeError, "Class method throws on access to 'caller' property", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
assert.throws(function() { obj.arguments; }, TypeError, "Class method throws on access to 'arguments' property", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context");
var a = new A();
assert.isFalse(a.hasOwnProperty('caller'), "Class instance does not report that it has own property 'caller'")
assert.isFalse(a.hasOwnProperty('arguments'), "Class instance does not report that it has own property 'arguments'")
assert.isFalse(a.__proto__.hasOwnProperty('caller'), "Class prototype does not report that it has own property 'caller'")
assert.isTrue(a.__proto__.hasOwnProperty('arguments'), "Class prototype has own property 'arguments'")
a.arguments = 50;
assert.areEqual(50, my_v, "Accessing the 'arguments' property was not restricted");
assert.areEqual(undefined, a.caller, "Access to class instance 'caller' property doesn't throw - instance is not a function");
a.caller = 123;
assert.areEqual(123, a.caller, "Assignment to class instance 'caller' property works normally");
}
},
{
name: "Restricted properties of lambda",
body: function () {
var obj = () => { }
verifyDoesNotHaveRestrictedOwnProperties(obj, "Lambda");
}
},
{
name: "Restricted properties of strict-mode lambda",
body: function () {
var obj = () => { 'use strict'; }
verifyDoesNotHaveRestrictedOwnProperties(obj, "Strict-mode lambda");
}
},
{
name: "Restricted properties of bound function",
body: function () {
function target() {}
var obj = target.bind(null);
verifyDoesNotHaveRestrictedOwnProperties(obj, "Bound function");
}
},
{
name: "Restricted properties of bound strict-mode function",
body: function () {
function target() { 'use strict'; }
var obj = target.bind(null);
verifyDoesNotHaveRestrictedOwnProperties(obj, "Bound strict-mode function");
}
},
{
name: "Restricted properties of generator function",
body: function () {
function* gf() { }
verifyDoesNotHaveRestrictedOwnProperties(gf, "Generator function");
}
},
{
name: "Restricted properties of strict-mode generator function",
body: function () {
function* gf() { 'use strict'; }
verifyDoesNotHaveRestrictedOwnProperties(gf, "Generator strict-mode function");
}
},
{
name: "Restricted properties of object-literal function",
body: function () {
var obj = { func() { } }
verifyHasRestrictedOwnProperties(obj.func, "Object-literal function");
}
},
{
name: "Restricted properties of strict-mode object-literal function",
body: function () {
var obj = { func() { 'use strict'; } }
verifyDoesNotHaveRestrictedOwnProperties(obj.func, "Object-literal strict-mode function");
}
},
];
testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });