Source code

Revision control

Copy as Markdown

Other Tools

function checkObjectFuse(obj, expected) {
var state = getObjectFuseState(obj);
assertEq(JSON.stringify(state), JSON.stringify(expected));
}
function markConstant(obj, key) {
assertEq(getObjectFuseState(obj).properties[key], "Untracked");
// This relies on the fact that storing to an Untracked property marks it
// Constant. Use getOwnPropertyDescriptor to prevent interacting with JIT
// optimizations.
obj[key] = Object.getOwnPropertyDescriptor(obj, key).value;
}
function testBasic() {
var obj = {};
addObjectFuse(obj);
checkObjectFuse(obj, {generation:0,properties:{}});
// Mutating a property changes the property state but not the generation.
obj.x = 1;
checkObjectFuse(obj, {generation:0,properties:{x:"Untracked"}});
markConstant(obj, "x");
checkObjectFuse(obj, {generation:0,properties:{x:"Constant"}});
obj.x = 2;
checkObjectFuse(obj, {generation:0,properties:{x:"NotConstant"}});
obj.x = 3;
checkObjectFuse(obj, {generation:0,properties:{x:"NotConstant"}});
// Setting a property to the same value also changes the state.
obj.a = undefined;
checkObjectFuse(obj, {generation:0,properties:{x:"NotConstant",a:"Untracked"}});
markConstant(obj, "a");
checkObjectFuse(obj, {generation:0,properties:{x:"NotConstant",a:"Constant"}});
assertEq(obj.a, undefined);
checkObjectFuse(obj, {generation:0,properties:{x:"NotConstant",a:"Constant"}});
obj.a = undefined;
checkObjectFuse(obj, {generation:0,properties:{x:"NotConstant",a:"NotConstant"}});
}
for (var i = 0; i < 20; i++) {
testBasic();
}
function testRemove() {
var obj = {};
addObjectFuse(obj);
checkObjectFuse(obj, {generation:0,properties:{}});
// Removing an untracked property doesn't change the generation.
obj.x = 1;
checkObjectFuse(obj, {generation:0,properties:{x:"Untracked"}});
delete obj.x;
checkObjectFuse(obj, {generation:0,properties:{}});
// Removing a constant property changes the generation.
obj.x = 1;
checkObjectFuse(obj, {generation:0,properties:{x:"Untracked"}});
markConstant(obj, "x");
checkObjectFuse(obj, {generation:0,properties:{x:"Constant"}});
delete obj.x;
checkObjectFuse(obj, {generation:1,properties:{}});
// Removing a no-longer-constant property must also change the generation
// because IC stubs may still have guards for this property.
obj.z = undefined;
markConstant(obj, "z");
checkObjectFuse(obj, {generation:1,properties:{z:"Constant"}});
obj.z = 2;
checkObjectFuse(obj, {generation:1,properties:{z:"NotConstant"}});
delete obj.z;
checkObjectFuse(obj, {generation:2,properties:{}});
// Test removing multiple properties.
obj.a = 1;
markConstant(obj, "a");
obj.b = 1;
markConstant(obj, "b");
obj.c = 1;
markConstant(obj, "c");
obj.d = 1;
markConstant(obj, "d");
obj.e = 1;
markConstant(obj, "e");
delete obj.b;
delete obj.d;
delete obj.e;
checkObjectFuse(obj, {generation:5,properties:{a:"Constant",c:"Constant"}});
}
for (var i = 0; i < 20; i++) {
testRemove();
}
function testMany() {
// Test adding and removing many properties.
var o = {};
addObjectFuse(o);
for (var i = 0; i < 1000; i++) {
o["prop" + i] = 1; // Untracked
o["prop" + i] = 1; // Constant
}
// Mark every third property NotConstant.
for (var i = 0; i < 1000; i += 3) {
o["prop" + i] = 3;
}
var state = getObjectFuseState(o);
assertEq(state.generation, 0);
for (var i = 0; i < 1000; i++) {
assertEq(state.properties["prop" + i], (i % 3) ? "Constant" : "NotConstant");
}
for (var i = 0; i < 1000; i += 2) {
delete o["prop" + i];
}
state = getObjectFuseState(o);
assertEq(state.generation, 500);
assertEq(Object.keys(state.properties).length, 500);
}
testMany();
testMany();
function testTeleporting() {
// Proto chain: a => b => c => d => null
var d = Object.create(null);
addObjectFuse(d);
var c = Object.create(d);
addObjectFuse(c);
var b = Object.create(c);
addObjectFuse(b);
var a = Object.create(b);
addObjectFuse(a);
d.x = 1;
checkObjectFuse(d, {generation:0,properties:{x:"Untracked"}});
// Shadowing an untracked property doesn't change the generation.
a.x = 1;
checkObjectFuse(a, {generation:0,properties:{x:"Untracked"}});
checkObjectFuse(d, {generation:0,properties:{x:"Untracked"}});
b.x = 1;
checkObjectFuse(b, {generation:0,properties:{x:"Untracked"}});
checkObjectFuse(d, {generation:0,properties:{x:"Untracked"}});
// Shadowing a constant property changes the generation.
d.y = 1;
markConstant(d, "y");
b.y = 1;
checkObjectFuse(b, {generation:0,properties:{x:"Untracked",y:"Untracked"}});
checkObjectFuse(d, {generation:1,properties:{x:"Untracked",y:"Constant"}});
// Shadowing a no-longer-constant property must also change the generation
// because IC stubs may still have guards for this property.
d.y = 3;
checkObjectFuse(d, {generation:1,properties:{x:"Untracked",y:"NotConstant"}});
c.y = 1;
checkObjectFuse(d, {generation:2,properties:{x:"Untracked",y:"NotConstant"}});
// Mutating the prototype of a non-prototype object doesn't change anything.
Object.setPrototypeOf(a, {});
checkObjectFuse(a, {generation:0,properties:{x:"Untracked"}});
checkObjectFuse(b, {generation:0,properties:{x:"Untracked",y:"Untracked"}});
checkObjectFuse(c, {generation:0,properties:{y:"Untracked"}});
checkObjectFuse(d, {generation:2,properties:{x:"Untracked",y:"NotConstant"}});
// Mutating the prototype of a prototype object bumps the generation counter.
Object.setPrototypeOf(b, {});
checkObjectFuse(a, {generation:0,properties:{x:"Untracked"}});
checkObjectFuse(b, {generation:1,properties:{x:"Untracked",y:"Untracked"}});
checkObjectFuse(c, {generation:1,properties:{y:"Untracked"}});
checkObjectFuse(d, {generation:3,properties:{x:"Untracked",y:"NotConstant"}});
Object.setPrototypeOf(c, {});
checkObjectFuse(a, {generation:0,properties:{x:"Untracked"}});
checkObjectFuse(b, {generation:1,properties:{x:"Untracked",y:"Untracked"}});
checkObjectFuse(c, {generation:2,properties:{y:"Untracked"}});
checkObjectFuse(d, {generation:4,properties:{x:"Untracked",y:"NotConstant"}});
}
for (var i = 0; i < 15; i++) {
testTeleporting();
}
function testAccessor() {
var o = {};
addObjectFuse(o);
// Add a data property.
o.x = 1;
markConstant(o, "x");
checkObjectFuse(o, {generation:0,properties:{x:"Constant"}});
// Change it to an accessor and check this marks the property non-constant.
Object.defineProperty(o, "x", {get: function() { return 1; }});
checkObjectFuse(o, {generation:0,properties:{x:"NotConstant"}});
Object.defineProperty(o, "x", {get: function() { return 1; }});
checkObjectFuse(o, {generation:0,properties:{x:"NotConstant"}});
// The same thing for accessor property => data property.
o = {};
addObjectFuse(o);
Object.defineProperty(o, "x", {get: function() { return 1; }, configurable: true});
checkObjectFuse(o, {generation:0,properties:{x:"Untracked"}});
Object.defineProperty(o, "x", {get: function() { return 1; }, configurable: true});
checkObjectFuse(o, {generation:0,properties:{x:"Constant"}});
Object.defineProperty(o, "x", {value: 3});
checkObjectFuse(o, {generation:0,properties:{x:"NotConstant"}});
}
for (var i = 0; i < 15; i++) {
testAccessor();
}
function testSwapNonProto() {
const obj = new FakeDOMObject();
addObjectFuse(obj);
obj.foo = 1;
obj.foo = 2;
obj.foo = 3;
checkObjectFuse(obj, {generation:0,properties:{foo:"NotConstant"}});
// Swapping an object bumps the generation and resets all property state.
const {transplant} = transplantableObject({object: obj});
transplant(this);
addObjectFuse(obj);
checkObjectFuse(obj, {generation:1,properties:{foo:"Untracked"}});
}
for (var i = 0; i < 15; i++) {
testSwapNonProto();
}
function testSwapProto() {
// Construct the following proto chain:
//
// receiver => protoA (FakeDOMObject) => protoB {prop: 567} => null
const protoB = Object.create(null);
protoB.prop = 567;
addObjectFuse(protoB);
markConstant(protoB, "prop");
const protoA = new FakeDOMObject();
addObjectFuse(protoA);
Object.setPrototypeOf(protoA, protoB);
const receiver = Object.create(protoA);
protoA.foo = 1;
protoA.foo = 2;
protoA.foo = 3;
checkObjectFuse(protoA, {generation:0,properties:{foo:"NotConstant"}});
checkObjectFuse(protoB, {generation:0,properties:{prop:"Constant"}});
// Swap protoA with another object. This must invalidate shape teleporting,
// because the proto chain of `receiver` now looks like this:
//
// receiver => protoA (new FakeDOMObject) => FakeDOMObject.prototype => Object.prototype => null
const {transplant} = transplantableObject({object: protoA});
transplant(this);
// The generation counter on protoA is bumped twice, for the proto mutation and
// for the object swap.
addObjectFuse(protoA);
checkObjectFuse(protoA, {generation:2,properties:{foo:"Untracked"}});
checkObjectFuse(protoB, {generation:1,properties:{prop:"Constant"}});
}
for (var i = 0; i < 15; i++) {
testSwapProto();
}