objectfuse-state.js (9081B)
1 function checkObjectFuse(obj, expected) { 2 var state = getObjectFuseState(obj); 3 assertEq(JSON.stringify(state), JSON.stringify(expected)); 4 } 5 function markConstant(obj, key) { 6 assertEq(getObjectFuseState(obj).properties[key], "Untracked"); 7 // This relies on the fact that storing to an Untracked property marks it 8 // Constant. Use getOwnPropertyDescriptor to prevent interacting with JIT 9 // optimizations. 10 obj[key] = Object.getOwnPropertyDescriptor(obj, key).value; 11 } 12 function testBasic() { 13 var obj = {}; 14 addObjectFuse(obj); 15 checkObjectFuse(obj, {generation:0,properties:{}}); 16 17 // Mutating a property changes the property state but not the generation. 18 obj.x = 1; 19 checkObjectFuse(obj, {generation:0,properties:{x:"Untracked"}}); 20 markConstant(obj, "x"); 21 checkObjectFuse(obj, {generation:0,properties:{x:"Constant"}}); 22 obj.x = 2; 23 checkObjectFuse(obj, {generation:0,properties:{x:"NotConstant"}}); 24 obj.x = 3; 25 checkObjectFuse(obj, {generation:0,properties:{x:"NotConstant"}}); 26 27 // Setting a property to the same value also changes the state. 28 obj.a = undefined; 29 checkObjectFuse(obj, {generation:0,properties:{x:"NotConstant",a:"Untracked"}}); 30 markConstant(obj, "a"); 31 checkObjectFuse(obj, {generation:0,properties:{x:"NotConstant",a:"Constant"}}); 32 assertEq(obj.a, undefined); 33 checkObjectFuse(obj, {generation:0,properties:{x:"NotConstant",a:"Constant"}}); 34 obj.a = undefined; 35 checkObjectFuse(obj, {generation:0,properties:{x:"NotConstant",a:"NotConstant"}}); 36 } 37 for (var i = 0; i < 20; i++) { 38 testBasic(); 39 } 40 41 function testRemove() { 42 var obj = {}; 43 addObjectFuse(obj); 44 checkObjectFuse(obj, {generation:0,properties:{}}); 45 46 // Removing an untracked property doesn't change the generation. 47 obj.x = 1; 48 checkObjectFuse(obj, {generation:0,properties:{x:"Untracked"}}); 49 delete obj.x; 50 checkObjectFuse(obj, {generation:0,properties:{}}); 51 52 // Removing a constant property changes the generation. 53 obj.x = 1; 54 checkObjectFuse(obj, {generation:0,properties:{x:"Untracked"}}); 55 markConstant(obj, "x"); 56 checkObjectFuse(obj, {generation:0,properties:{x:"Constant"}}); 57 delete obj.x; 58 checkObjectFuse(obj, {generation:1,properties:{}}); 59 60 // Removing a no-longer-constant property must also change the generation 61 // because IC stubs may still have guards for this property. 62 obj.z = undefined; 63 markConstant(obj, "z"); 64 checkObjectFuse(obj, {generation:1,properties:{z:"Constant"}}); 65 obj.z = 2; 66 checkObjectFuse(obj, {generation:1,properties:{z:"NotConstant"}}); 67 delete obj.z; 68 checkObjectFuse(obj, {generation:2,properties:{}}); 69 70 // Test removing multiple properties. 71 obj.a = 1; 72 markConstant(obj, "a"); 73 obj.b = 1; 74 markConstant(obj, "b"); 75 obj.c = 1; 76 markConstant(obj, "c"); 77 obj.d = 1; 78 markConstant(obj, "d"); 79 obj.e = 1; 80 markConstant(obj, "e"); 81 delete obj.b; 82 delete obj.d; 83 delete obj.e; 84 checkObjectFuse(obj, {generation:5,properties:{a:"Constant",c:"Constant"}}); 85 } 86 for (var i = 0; i < 20; i++) { 87 testRemove(); 88 } 89 90 function testMany() { 91 // Test adding and removing many properties. 92 var o = {}; 93 addObjectFuse(o); 94 for (var i = 0; i < 1000; i++) { 95 o["prop" + i] = 1; // Untracked 96 o["prop" + i] = 1; // Constant 97 } 98 // Mark every third property NotConstant. 99 for (var i = 0; i < 1000; i += 3) { 100 o["prop" + i] = 3; 101 } 102 var state = getObjectFuseState(o); 103 assertEq(state.generation, 0); 104 for (var i = 0; i < 1000; i++) { 105 assertEq(state.properties["prop" + i], (i % 3) ? "Constant" : "NotConstant"); 106 } 107 for (var i = 0; i < 1000; i += 2) { 108 delete o["prop" + i]; 109 } 110 state = getObjectFuseState(o); 111 assertEq(state.generation, 500); 112 assertEq(Object.keys(state.properties).length, 500); 113 } 114 testMany(); 115 testMany(); 116 117 function testTeleporting() { 118 // Proto chain: a => b => c => d => null 119 var d = Object.create(null); 120 addObjectFuse(d); 121 var c = Object.create(d); 122 addObjectFuse(c); 123 var b = Object.create(c); 124 addObjectFuse(b); 125 var a = Object.create(b); 126 addObjectFuse(a); 127 128 d.x = 1; 129 checkObjectFuse(d, {generation:0,properties:{x:"Untracked"}}); 130 131 // Shadowing an untracked property doesn't change the generation. 132 a.x = 1; 133 checkObjectFuse(a, {generation:0,properties:{x:"Untracked"}}); 134 checkObjectFuse(d, {generation:0,properties:{x:"Untracked"}}); 135 b.x = 1; 136 checkObjectFuse(b, {generation:0,properties:{x:"Untracked"}}); 137 checkObjectFuse(d, {generation:0,properties:{x:"Untracked"}}); 138 139 // Shadowing a constant property changes the generation. 140 d.y = 1; 141 markConstant(d, "y"); 142 b.y = 1; 143 checkObjectFuse(b, {generation:0,properties:{x:"Untracked",y:"Untracked"}}); 144 checkObjectFuse(d, {generation:1,properties:{x:"Untracked",y:"Constant"}}); 145 146 // Shadowing a no-longer-constant property must also change the generation 147 // because IC stubs may still have guards for this property. 148 d.y = 3; 149 checkObjectFuse(d, {generation:1,properties:{x:"Untracked",y:"NotConstant"}}); 150 c.y = 1; 151 checkObjectFuse(d, {generation:2,properties:{x:"Untracked",y:"NotConstant"}}); 152 153 // Mutating the prototype of a non-prototype object doesn't change anything. 154 Object.setPrototypeOf(a, {}); 155 checkObjectFuse(a, {generation:0,properties:{x:"Untracked"}}); 156 checkObjectFuse(b, {generation:0,properties:{x:"Untracked",y:"Untracked"}}); 157 checkObjectFuse(c, {generation:0,properties:{y:"Untracked"}}); 158 checkObjectFuse(d, {generation:2,properties:{x:"Untracked",y:"NotConstant"}}); 159 160 // Mutating the prototype of a prototype object bumps the generation counter. 161 Object.setPrototypeOf(b, {}); 162 checkObjectFuse(a, {generation:0,properties:{x:"Untracked"}}); 163 checkObjectFuse(b, {generation:1,properties:{x:"Untracked",y:"Untracked"}}); 164 checkObjectFuse(c, {generation:1,properties:{y:"Untracked"}}); 165 checkObjectFuse(d, {generation:3,properties:{x:"Untracked",y:"NotConstant"}}); 166 167 Object.setPrototypeOf(c, {}); 168 checkObjectFuse(a, {generation:0,properties:{x:"Untracked"}}); 169 checkObjectFuse(b, {generation:1,properties:{x:"Untracked",y:"Untracked"}}); 170 checkObjectFuse(c, {generation:2,properties:{y:"Untracked"}}); 171 checkObjectFuse(d, {generation:4,properties:{x:"Untracked",y:"NotConstant"}}); 172 } 173 for (var i = 0; i < 15; i++) { 174 testTeleporting(); 175 } 176 177 function testAccessor() { 178 var o = {}; 179 addObjectFuse(o); 180 181 // Add a data property. 182 o.x = 1; 183 markConstant(o, "x"); 184 checkObjectFuse(o, {generation:0,properties:{x:"Constant"}}); 185 186 // Change it to an accessor and check this marks the property non-constant. 187 Object.defineProperty(o, "x", {get: function() { return 1; }}); 188 checkObjectFuse(o, {generation:0,properties:{x:"NotConstant"}}); 189 Object.defineProperty(o, "x", {get: function() { return 1; }}); 190 checkObjectFuse(o, {generation:0,properties:{x:"NotConstant"}}); 191 192 // The same thing for accessor property => data property. 193 o = {}; 194 addObjectFuse(o); 195 Object.defineProperty(o, "x", {get: function() { return 1; }, configurable: true}); 196 checkObjectFuse(o, {generation:0,properties:{x:"Untracked"}}); 197 Object.defineProperty(o, "x", {get: function() { return 1; }, configurable: true}); 198 checkObjectFuse(o, {generation:0,properties:{x:"Constant"}}); 199 Object.defineProperty(o, "x", {value: 3}); 200 checkObjectFuse(o, {generation:0,properties:{x:"NotConstant"}}); 201 } 202 for (var i = 0; i < 15; i++) { 203 testAccessor(); 204 } 205 206 function testSwapNonProto() { 207 const obj = new FakeDOMObject(); 208 addObjectFuse(obj); 209 obj.foo = 1; 210 obj.foo = 2; 211 obj.foo = 3; 212 checkObjectFuse(obj, {generation:0,properties:{foo:"NotConstant"}}); 213 214 // Swapping an object bumps the generation and resets all property state. 215 const {transplant} = transplantableObject({object: obj}); 216 transplant(this); 217 addObjectFuse(obj); 218 checkObjectFuse(obj, {generation:1,properties:{foo:"Untracked"}}); 219 } 220 for (var i = 0; i < 15; i++) { 221 testSwapNonProto(); 222 } 223 224 function testSwapProto() { 225 // Construct the following proto chain: 226 // 227 // receiver => protoA (FakeDOMObject) => protoB {prop: 567} => null 228 const protoB = Object.create(null); 229 protoB.prop = 567; 230 addObjectFuse(protoB); 231 markConstant(protoB, "prop"); 232 const protoA = new FakeDOMObject(); 233 addObjectFuse(protoA); 234 Object.setPrototypeOf(protoA, protoB); 235 const receiver = Object.create(protoA); 236 237 protoA.foo = 1; 238 protoA.foo = 2; 239 protoA.foo = 3; 240 checkObjectFuse(protoA, {generation:0,properties:{foo:"NotConstant"}}); 241 checkObjectFuse(protoB, {generation:0,properties:{prop:"Constant"}}); 242 243 // Swap protoA with another object. This must invalidate shape teleporting, 244 // because the proto chain of `receiver` now looks like this: 245 // 246 // receiver => protoA (new FakeDOMObject) => FakeDOMObject.prototype => Object.prototype => null 247 const {transplant} = transplantableObject({object: protoA}); 248 transplant(this); 249 250 // The generation counter on protoA is bumped twice, for the proto mutation and 251 // for the object swap. 252 addObjectFuse(protoA); 253 checkObjectFuse(protoA, {generation:2,properties:{foo:"Untracked"}}); 254 checkObjectFuse(protoB, {generation:1,properties:{prop:"Constant"}}); 255 } 256 for (var i = 0; i < 15; i++) { 257 testSwapProto(); 258 }