blob: d97a04dde163b99d3e92c1a7d5aa24a41343c5d5 [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.
//-------------------------------------------------------------------------------------------------------
/// <reference path="protolib.js" />
if (this.WScript && this.WScript.LoadScriptFile) {
WScript.LoadScriptFile("protolib.js");
}
function printProtoChain(obj) {
var known = [
Number.prototype, Boolean.prototype, String.prototype, Object.prototype, Array.prototype, Function.prototype,
];
var names = [
"Number.prototype", "Boolean.prototype", "String.prototype", "Object.prototype", "Array.prototype", "Function.prototype",
];
var s = "";
while (obj) {
var i = known.indexOf(obj);
var name = (i >= 0 ? names[i] : JSON.stringify(obj));
if (s == "") {
s = name;
} else {
s += " -> " + name;
}
obj = Object.getPrototypeOf(obj);
}
helpers.writeln(s);
}
function make_point(_x, _y) {
return { x: _x, y: _y };
}
var tests;
if (helpers.isVersion10OrLater) { // TODO: Change to _11_OrLater
tests = [
{
name: "Test Object.prototype.__proto__ attributes",
body: function () {
var desc = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
helpers.printObject(desc);
}
},
{
name: "Read built-in __proto__",
body: function () {
assert.isTrue(12["__proto__"] === Number.prototype, "int __proto__");
assert.isTrue(12.3["__proto__"] === Number.prototype, "float __proto__");
assert.isTrue((new Number(12)).__proto__ === Number.prototype, "Number Object __proto__");
assert.isTrue(true.__proto__ === Boolean.prototype, "Boolean Value __proto__");
assert.isTrue(false.__proto__ === Boolean.prototype, "Boolean Value __proto__");
assert.isTrue((new Boolean(true)).__proto__ === Boolean.prototype, "Boolean Object __proto__");
assert.isTrue((new Boolean(false)).__proto__ === Boolean.prototype, "Boolean Object __proto__");
assert.isTrue("hello".__proto__ === String.prototype, "String Value __proto__");
assert.isTrue(new String("hello").__proto__ === String.prototype, "String Object __proto__");
assert.isTrue({}.__proto__ === Object.prototype, "{} __proto__");
assert.isTrue([].__proto__ === Array.prototype, "[] __proto__");
assert.isTrue(Array.prototype.__proto__ === Object.prototype, "Array.prototype.__proto__");
assert.isTrue(Object.prototype.__proto__ === null, "Object.prototype.__proto__");
assert.isTrue(Array.__proto__ === Function.prototype, "Array.__proto__");
assert.isTrue(Function.__proto__ === Function.prototype, "Function.__proto__");
assert.isTrue(Function.prototype.__proto__ === Object.prototype, "Function.prototype.__proto__");
var o = { a: 0 };
assert.isTrue(o.__proto__ === Object.prototype, "o.__proto__");
assert.isTrue(o.__proto__.__proto__ === null, "o.__proto__.__proto__");
}
},
{
name: "Change built-in's __proto__ chain",
body: function () {
function changeProtoAndTest(obj, newProto, test) {
var oldProto = obj.__proto__;
obj.__proto__ = newProto;
printProtoChain(obj);
try {
test();
} finally {
obj.__proto__ = oldProto; // Restore old __proto__
}
}
// Number doesn't have sort method, should throw
assert.throws(function () { 12["sort"]() }, TypeError, "Object doesn't support property or method 'sort'");
// Number gets Array methods from new prototype in the chain
changeProtoAndTest(Number.prototype, Array.prototype, function () {
12["sort"]();
var o = new Number(34);
o[0] = 8;
o[1] = 3;
o[2] = 9;
o[3] = 5;
o.length = 4;
assert.areEqual("8 3 9 5", o.join(" "));
o.sort();
assert.areEqual("3 5 8 9", o.join(" "));
});
// Boolean doesn't have String methods
assert.throws(function () { true.toUpperCase() }, TypeError, "Object doesn't support property or method 'toUpperCase'");
// Boolean gets String methods from new prototype in the chain
changeProtoAndTest(Boolean.prototype, new String("abc"), function () {
assert.areEqual("TRUE", true.toUpperCase());
assert.areEqual("FALSE", false.toUpperCase());
assert.areEqual("TRUE", (new Boolean(true)).toUpperCase());
assert.areEqual("FALSE", (new Boolean(false)).toUpperCase());
});
}
},
{
name: "Change __proto__ to null/undefined",
body: function () {
var a = {};
// Set an object's prototype to undefined
{
var a = {};
assert.isTrue(a.__proto__ === Object.prototype);
assert.isTrue(!a.isPrototypeOf(a));
a.__proto__ = undefined;
assert.isTrue(a.__proto__ === Object.prototype);
assert.isTrue(!a.isPrototypeOf(a));
assert.throws__proto__ArgNotObjectOrNull(function () { Object.setPrototypeOf(a, undefined); }, "Object.setPrototypeOf");
}
// Set an object's prototype to null
function f1(O, P) { O.__proto__ = P; }
function f2(O, P) { Object.setPrototypeOf(O, P); }
[f1, f2].forEach(function (f) {
var a = {};
assert.isTrue(a.__proto__ === Object.prototype);
assert.isTrue(!a.isPrototypeOf(a));
f(a, null);
assert.isTrue(Object.getPrototypeOf(a) === null);
assert.isTrue(a.__proto__ === undefined, "lost Object.prototype __proto__ getter");
assert.isTrue(a.isPrototypeOf === undefined, "lost Object.prototype methods");
});
// side test -- try to change null/undefined's __proto__ should throw
var __proto__setter = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__").set;
[null, undefined].forEach(function (o) {
assert.throws__proto__ThisNotObject(function () {
__proto__setter.apply(o, [{}]);
});
assert.throws__proto__ArgNotObject(function () {
Object.setPrototypeOf(o, {});
});
});
}
},
{
name: "Change __proto__ to neither Object nor null, should throw",
body: function () {
function assertThrowAndNoChange(proto) {
var a = {};
a.__proto__ = proto;
assert.isTrue(Object.getPrototypeOf(a) === Object.prototype, "Change __proto__ to: " + proto);
assert.throws__proto__ArgNotObjectOrNull(function () {
Object.setPrototypeOf(a, proto);
}, "Object.setPrototypeOf");
assert.isTrue(Object.getPrototypeOf(a) === Object.prototype, "Change __proto__ to: " + proto);
}
var __proto__setter = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__").set;
[
undefined, 0, 123, -12.3, NaN, Infinity, true, false, "str"
].forEach(function (proto) {
assertThrowAndNoChange(proto);
// side test -- try to change these primitive's __proto__ should pass (but has no effect)
if (proto !== undefined) {
proto.__proto__ = {}; // This is ok
__proto__setter.apply(proto, [{}]); // This is ok
Object.setPrototypeOf(proto, {});
}
});
}
},
{
name: "Change object.__proto__ to an object",
body: function () {
var a = { a: 100, b: 100 };
var b = { b: 200, c: 200 };
b.__proto__ = a;
helpers.printObject(b);
helpers.writeln("\n-- delete b.b --");
delete b.b;
helpers.printObject(b);
}
},
{
name: "Change object.__proto__ to an array",
body: function () {
var a = [8, 3, 9, 5];
var b = { b: 200 };
// b.sort not available
assert.throws(function () { b.sort(); });
b.__proto__ = a;
assert.isTrue(!Array.isArray(b), "Still not Array");
helpers.printObject(b);
helpers.writeln("\n-- a.sort --");
a.sort();
helpers.printObject(b);
helpers.writeln("\n-- b.sort --");
b.sort();
helpers.printObject(b);
}
},
{
name: "Change array.__proto__ to an object",
body: function () {
var a = { a: 100 };
var b = [8, 3, 9, 5];
b.__proto__ = a;
assert.isTrue(Array.isArray(b), "Still is Array");
helpers.printObject(b);
// But now b.sort is gone
assert.throws(function () { b.sort(); });
// We can sort by apply
helpers.writeln("\n-- sort.apply --");
Array.prototype.sort.apply(b);
helpers.printObject(b);
}
},
{
name: "Attempt to change DOM object __proto__",
body: function () {
function test(domObj, expected) {
var p0 = Object.getPrototypeOf(domObj);
assert.isTrue(p0 === domObj.__proto__);
var p1 = { __proto__: p0 };
domObj.__proto__ = p1;
assert.areEqual(expected, Object.getPrototypeOf(domObj) === p1);
assert.isTrue(expected || domObj.document !== null); // expected succeed or otherwise window
}
if (helpers.isInBrowser()) {
test(this, false);
test(window, false);
test(window.frames[0], false);
test(document, true);
test(document.body, true);
test(document.createElement("div"), true);
// test form scope
var f = document.getElementById("form1");
var p0 = f.__proto__;
f.__proto__ = { form1_injected: "form1_injected_value", __proto__: p0 };
var button1 = document.getElementById("button1");
button1.click();
assert.areEqual("form1_injected_value", button1.form1_injected_copy);
}
}
},
{
name: "__proto__ and instanceof/isPrototypeOf",
body: function () {
function A() { }
function B() { }
var a = new A();
var b = new B();
assert.isTrue(!(b instanceof A), "Initially b is not instance of A");
assert.isTrue(!(A.prototype.isPrototypeOf(b)));
assert.isTrue(b instanceof B, "Initially b is instance of B");
assert.isTrue(B.prototype.isPrototypeOf(b));
b.__proto__ = A.prototype;
assert.isTrue(b instanceof A, "Now b is instance of A");
assert.isTrue(A.prototype.isPrototypeOf(b));
assert.isTrue(!(b instanceof B), "Now b is not instance of B");
assert.isTrue(!(B.prototype.isPrototypeOf(b)));
b.__proto__ = a;
assert.isTrue(b instanceof A, "b is still instance of A");
assert.isTrue(A.prototype.isPrototypeOf(b));
assert.isTrue(!(b instanceof B), "b is still not instance of B");
assert.isTrue(!(B.prototype.isPrototypeOf(b)));
b.__proto__ = B.prototype;
assert.isTrue(!(b instanceof A), "b is back not instance of A");
assert.isTrue(!(A.prototype.isPrototypeOf(b)));
assert.isTrue(b instanceof B, "b is back instance of B");
assert.isTrue(B.prototype.isPrototypeOf(b));
}
},
{
name: "Verify we reject simple __proto__ cycle",
body: function () {
var a = {};
var b = {};
var c = {};
a.__proto__ = b;
b.__proto__ = c;
assert.throws__proto__Cyclic(function () { c.__proto__ = a });
assert.throws__proto__Cyclic(function () { a.__proto__ = a });
assert.throws__proto__Cyclic(function () { Array.prototype.__proto__ = [] });
}
},
{
name: "Verify proto cache is discarded",
body: function () {
var a = make_point(1, 2);
var b = make_point(3, 4);
//
// Create prototype chain:
// x -> y -> a
// where x inherits properties from prototype a.
//
var y = Object.create(a);
var x = Object.create(y);
function print(o) {
helpers.writeln(o.x + ", " + o.y);
}
helpers.writeln("Before change");
print(x);
//
// Now change prototype chain:
// x -> y -> b
// While changing y.__proto__, we should discard inline proto cache in original chain.
// x should now get changed properties from prototype b.
//
helpers.writeln("After change");
y.__proto__ = b;
print(x);
}
},
{
// Copy of above proto cache test and change a getter
name: "Verify proto getter cache is discarded",
body: function () {
var a = make_point(1, 2);
var b = make_point(3, 4);
Object.defineProperty(a, "y", { get: function () { return 7; }, enumerable: true, configurable: true });
//
// Create prototype chain:
// x -> y -> a
// where x inherits properties from prototype a.
//
var y = Object.create(a);
var x = Object.create(y);
function print(o) {
helpers.writeln(o.x + ", " + o.y);
}
helpers.writeln("Before change");
print(x);
//
// Now change prototype chain:
// x -> y -> b
// While changing y.__proto__, we should discard inline proto cache in original chain.
// x should now get changed properties from prototype b.
//
helpers.writeln("After change");
y.__proto__ = b;
print(x);
},
},
{
name: "Verify the new proto object is marked as proto, so that changing the proto object invalidates related proto cache",
body: function () {
var b = make_point(3, 4);
function print(o) {
helpers.writeln(o.x + ", " + o.y);
}
var x = {};
helpers.writeln("Before change");
print(x);
helpers.writeln("After change");
x.__proto__ = b; // This should mark "b" as a prototype object
print(x);
//
// Now we make certain change to "b". If b is marked on prototype correctly, the following should invalidate related proto cache.
//
helpers.writeln("After change proto property");
Object.defineProperty(b, "x", { get: function () { return 9; }, enumerable: true, configurable: true });
print(x);
}
},
{
name: "Verify changing __proto__ works safely with ObjTypeSpec",
body: function () {
function f(o, a) {
var m = o.x;
if (a > 0)
{
o.__proto__ = {}; // ObjTypeSpec won't generate 2nd type check for the next "o.x" load. But implicit call bailout should bailout right here.
}
var n = o.x;
assert.areEqual(1, m);
assert.areEqual(a > 0 ? undefined : 1, n);
}
var p = { x: 1 };
var o = Object.create(p);
f(o, 0); // no __proto__ change in interpreter.
f(o, 0); // -maxInterpretCount:1, still no __proto__ change in jit.
f(o, 1); // -maxInterpretCount:1, now with __proto__ change in jit.
}
},
{
name: "Verify PathTypeHandler successor Types continue to work, case 1",
body: function () {
function f() {
return { x_100: 1 }; // Use x_100 to start a unique path
}
var o1 = f();
o1.__proto__ = { a: 2 };
o1.y = 1; // This has path "x" -> "x, y"
var o2 = f();
o2.y = 1; // This also has path "x" -> "x, y"
// But o1 and o2 can't go to the same Type. If they do, they have the same [[prototype]], which is wrong.
assert.areEqual(2, o1.a, "from prototype");
assert.areEqual(undefined, o2.a, "should be undefined");
}
},
{
name: "Verify PathTypeHandler successor Types continue to work, case 2",
body: function () {
function f() {
return { x_101: 1 }; // Use x_101 to start a unique path
}
var o1 = f();
o1.y = 1; // This has path "x" -> "x, y"
var o2 = f();
o2.__proto__ = { a: 2 };
o2.y = 1; // This also has path "x" -> "x, y"
// But o1 and o2 can't go to the same Type. If they do, they have the same [[prototype]], which is wrong.
assert.areEqual(undefined, o1.a, "should be undefined");
assert.areEqual(2, o2.a, "from prototype");
}
},
{
name: "Verify that we can shadow __proto__ property",
body: function () {
var p = {};
var o = {};
o.__proto__ = p;
Object.defineProperty(p, "__proto__", { value: 10, writable: true, enumerable: true, configurable: true });
assert.areEqual(10, o.__proto__);
o.__proto__ = -100;
assert.areEqual(-100, o.__proto__);
delete o.__proto__;
assert.areEqual(10, o.__proto__);
delete p.__proto__;
assert.areEqual(p, o.__proto__);
assert.areEqual(Object.prototype, p.__proto__);
o.__proto__ = { a: 123 };
assert.areEqual(123, o.a);
}
},
{
name: "Test fast path o[i] with changed prototype",
body: function () {
var o = []; o.length = 10;
var p = [123];
o.__proto__ = p; // Now o's prototype != Array.prototype
assert.areEqual(123, o[0]);
var p0 = { "1": 4 };
p.__proto__ = p0; // Now p's prototype != Object.prototype
assert.areEqual(4, o[1]);
p0.__proto__ = null;
assert.areEqual(undefined, o[2]);
}
},
{
name: "Test fast path o[i] when o.__proto__ == null",
body: function () {
var o = [];
o.__proto__ = null;
assert.areEqual(undefined, o[1]);
}
},
{
name: "Test fast path o[i] when o.__proto__.__proto__ == null",
body: function () {
var o = [];
o.__proto__ = [12, 34];
o.__proto__.__proto__ = null;
assert.areEqual(undefined, o[5]);
}
},
{
name: "Test Array methods with changed prototype",
body: function () {
function make_array() {
var arr = [];
for (var p in arguments) {
var i = arguments[p];
arr[i] = i;
}
return arr;
}
var o = []; o.length = 10;
o.__proto__ = make_array(0, 1, 2);
o.__proto__.__proto__ = make_array(3, 4);
o.__proto__.__proto__.__proto__ = { "5": 5, "6": 6 };
o.__proto__.__proto__.__proto__.__proto__ = Array.prototype;
var a = o.slice();
assert.areEqual("0,1,2,3,4,5,6,,,", a.toString());
a.__proto__ = make_array(8, 9);
assert.areEqual("0,1,2,3,4,5,6,,8,9", a.toString());
}
},
{
name: "Test cross-site change prototype",
body: function () {
if (this.WScript.LoadScriptFile) {
var eng = make_engine();
var other = eng.eval('var o = { get name() {return this.v}, set name(value) {this.v = value}, v: "a name" }; o');
var o = { __proto__: other };
assert.areEqual("a name", o.name);
}
}
},
{
name: "Test change prototype of global object",
body: function () {
if (this.WScript.LoadScriptFile) {
make_engine().run(function () {
function test(x) {
var p = { a: 1 };
// Should go through general change prototype code path and mark "p" as prototype. Then changes to "p" should invalidate proto cache.
x.__proto__ = p;
assert.areEqual(1, x.a);
Object.defineProperty(p, "a", { get: function () { return 2; }, configurable: true });
assert.areEqual(2, x.a);
}
assert.isTrue(__proto__ === Object.prototype); // default binds to global this.__proto__
test(this);
__proto__ = { b: 3, __proto__: __proto__ };
assert.areEqual(2, a);
assert.areEqual(3, b);
});
}
}
},
{
name: "Blue 62526: __proto__: ArrayElementEnumerator does not expect non-TypeIds_Object on prototype",
body: function () {
var a = new String();
a[1] = "a1";
a[2] = "a2";
a.__proto__ = [];
var b = [0];
b.__proto__ = a;
b.length = 5;
b = b.concat([]);
assert.areEqual('[0,"a1","a2",null,null]', JSON.stringify(b));
}
},
{
name: "Blue 114364: __proto__: Object.preventExtensions should make [[prototype]] immutable",
body: function () {
var a = {};
Object.preventExtensions(a);
assert.throws(
function () { a.__proto__ = { x: 1 } },
TypeError);
assert.isTrue(a.__proto__ === Object.prototype, "__proto__ should remain unchanged");
}
},
{
name: "Blue 245453: __proto__: Invalid has-only-writable-data-property cache",
body: function () {
var a = { __proto__: {} };
a.x = 0; // populate cache -- whole prototype chain has only writable data properties
a.__proto__.__proto__ = /a_regex/;
a.source = 1;
assert.areEqual("a_regex", a.source, "prototype chain should NOT has-only-writable-data-properties");
}
},
{
name: "Blue 245453: __proto__: Invalid has-only-writable-data-property cache -- verify cross-context",
body: function () {
if (this.WScript.LoadScriptFile) {
var eng = make_engine();
var other = eng.eval('({b: 1})');
var a = { __proto__: other };
assert.areEqual(1, a.b, "a.b from prototype");
a.x = 0; // try to populate cache -- prototype chain has only writable data properties -- our code actually would not cache cross-context
Object.defineProperty(other, "y", { value: 2, enumerable: true, writable: false });
a.y = 1234;
assert.areEqual(2, a.y, "prototype chain should NOT be has-only-writable-data-properties");
}
}
},
];
} else {
tests = [
{
name: "Test Object.prototype.__proto__",
body: function () {
assert.isTrue(Object.prototype.__proto__ === undefined, "Object.prototype.__proto__ only supported on IE11 or later");
assert.isTrue(Object.hasOwnProperty("getPrototypeOf") && !Object.hasOwnProperty("setPrototypeOf"), "Object.setPrototypeOf only supported on IE11 or later");
var o = { __proto__: { pp: 123 } }; // compat mode: not working in object literal
assert.areEqual(undefined, o.pp);
}
}
];
}
testRunner.run(tests);