tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }