tor-browser

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

browser_allocation_tracker.js (7854B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Load the tracker from a distinct compartment so that it can inspect any other
      7 // module/compartment, even DevTools, chrome, and this script!
      8 const {
      9  useDistinctSystemPrincipalLoader,
     10  releaseDistinctSystemPrincipalLoader,
     11 } = ChromeUtils.importESModule(
     12  "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs",
     13  { global: "shared" }
     14 );
     15 
     16 const requester = {};
     17 const loader = useDistinctSystemPrincipalLoader(requester);
     18 registerCleanupFunction(() => releaseDistinctSystemPrincipalLoader(requester));
     19 
     20 const { allocationTracker } = loader.require(
     21  "chrome://mochitests/content/browser/devtools/shared/test-helpers/allocation-tracker"
     22 );
     23 const TrackedObjects = loader.require(
     24  "resource://devtools/shared/test-helpers/tracked-objects.sys.mjs"
     25 );
     26 
     27 // This test record multiple times complete heap snapshot,
     28 // so that it can take a little bit to complete.
     29 requestLongerTimeout(2);
     30 
     31 add_task(async function () {
     32  // Use a sandbox to allocate test javascript object in order to avoid any
     33  // external noise
     34  const global = Cu.Sandbox("http://example.com");
     35 
     36  const tracker = allocationTracker({ watchGlobal: global });
     37  const before = tracker.stillAllocatedObjects();
     38 
     39  /* eslint-disable no-undef */
     40  // This will allocation 1001 objects. The array and 1000 elements in it.
     41  Cu.evalInSandbox(
     42    "let list; new " +
     43      function () {
     44        list = [];
     45        for (let i = 0; i < 1000; i++) {
     46          list.push({});
     47        }
     48      },
     49    global,
     50    undefined,
     51    "test-file.js",
     52    1,
     53    /* enforceFilenameRestrictions */ false
     54  );
     55  /* eslint-enable no-undef */
     56 
     57  const allocations = tracker.countAllocations();
     58  Assert.greaterOrEqual(
     59    allocations,
     60    1001,
     61    `At least 1001 objects are reported as created (${allocations})`
     62  );
     63 
     64  // Uncomment this and comment the call to `countAllocations` to debug the allocations.
     65  // The call to `countAllocations` will reset the allocation record.
     66  // tracker.logAllocationSites();
     67 
     68  const afterCreation = tracker.stillAllocatedObjects();
     69  is(
     70    afterCreation.objectsWithStack - before.objectsWithStack,
     71    1001,
     72    "We got exactly the expected number of objects recorded with an allocation site"
     73  );
     74  Assert.greater(
     75    afterCreation.objectsWithStack,
     76    before.objectsWithStack,
     77    "We got some random number of objects without an allocation site"
     78  );
     79 
     80  Cu.evalInSandbox(
     81    "list = null;",
     82    global,
     83    undefined,
     84    "test-file.js",
     85    7,
     86    /* enforceFilenameRestrictions */ false
     87  );
     88 
     89  Cu.forceGC();
     90  Cu.forceCC();
     91 
     92  const afterGC = tracker.stillAllocatedObjects();
     93  is(
     94    afterCreation.objectsWithStack - afterGC.objectsWithStack,
     95    1001,
     96    "All the expected objects were reported freed in the count with allocation sites"
     97  );
     98  Assert.less(
     99    afterGC.objectsWithoutStack,
    100    afterCreation.objectsWithoutStack,
    101    "And we released some random number of objects without an allocation site"
    102  );
    103 
    104  tracker.stop();
    105 });
    106 
    107 add_task(async function () {
    108  const leaked = {};
    109  TrackedObjects.track(leaked);
    110  let transient = {};
    111  TrackedObjects.track(transient);
    112 
    113  is(
    114    TrackedObjects.getStillAllocatedObjects().length,
    115    2,
    116    "The two objects are reported"
    117  );
    118 
    119  info("Free the transient object");
    120  transient = null;
    121  Cu.forceGC();
    122 
    123  is(
    124    TrackedObjects.getStillAllocatedObjects().length,
    125    1,
    126    "We now only have the leaked object"
    127  );
    128  is(
    129    TrackedObjects.getStillAllocatedObjects()[0].weakRef.get(),
    130    leaked,
    131    "The still allocated objects is the leaked one"
    132  );
    133  TrackedObjects.clear();
    134 });
    135 
    136 add_task(async function () {
    137  info("Test start and stop recording without any debug mode");
    138  const tracker = allocationTracker({ watchDevToolsGlobals: true });
    139  await tracker.startRecordingAllocations();
    140  await tracker.stopRecordingAllocations();
    141  tracker.stop();
    142 });
    143 
    144 add_task(async function () {
    145  info("Test start and stop recording with 'allocations' debug mode");
    146  const tracker = allocationTracker({ watchDevToolsGlobals: true });
    147  await tracker.startRecordingAllocations("allocations");
    148  await tracker.stopRecordingAllocations("allocations");
    149  tracker.stop();
    150 });
    151 
    152 add_task(async function () {
    153  info("Test start and stop recording with 'leaks' debug mode");
    154  const tracker = allocationTracker({ watchDevToolsGlobals: true });
    155  await tracker.startRecordingAllocations("leaks");
    156  await tracker.stopRecordingAllocations("leaks");
    157  tracker.stop();
    158 });
    159 
    160 add_task(async function () {
    161  info("Test start and stop recording with tracked objects");
    162 
    163  const leaked = {};
    164  TrackedObjects.track(leaked);
    165 
    166  const tracker = allocationTracker({ watchAllGlobals: true });
    167  await tracker.startRecordingAllocations();
    168  await tracker.stopRecordingAllocations();
    169  tracker.stop();
    170 
    171  TrackedObjects.clear();
    172 });
    173 
    174 add_task(async function () {
    175  info("Test start and stop recording with tracked objects");
    176 
    177  const sandbox = Cu.Sandbox(window);
    178  const tracker = allocationTracker({ watchGlobal: sandbox });
    179  await tracker.startRecordingAllocations("leaks");
    180 
    181  Cu.evalInSandbox("this.foo = {};", sandbox, null, "sandbox.js", 1);
    182 
    183  const record = await tracker.stopRecordingAllocations("leaks");
    184  is(
    185    record.objectsWithStack,
    186    1,
    187    "We get only one leaked objects, the foo object of the sandbox."
    188  );
    189  Assert.greater(
    190    record.objectsWithoutStack,
    191    10,
    192    "We get an handful of objects without stacks. Most likely created by Memory API itself."
    193  );
    194 
    195  is(
    196    record.leaks.length,
    197    2,
    198    "We get the one leak and the objects with missing stacks"
    199  );
    200  is(
    201    record.leaks[0].src,
    202    "UNKNOWN",
    203    "First item is the objects with missing stacks"
    204  );
    205  // In theory the two following values should be equal,
    206  // but they aren't always because of some dark matter around objects with missing stacks.
    207  // `count` is computed out of `takeCensus`, while `objectsWithoutStack` uses `drainAllocationsLog`
    208  // While the first go through the current GC graph, the second is a record of allocations over time,
    209  // this probably explain why there is some subtle difference
    210  Assert.lessOrEqual(
    211    record.leaks[0].count,
    212    record.objectsWithoutStack,
    213    "For now, the leak report intermittently assume there is less leaked objects than the summary"
    214  );
    215  is(record.leaks[1].src, "sandbox.js", "Second item if about our 'foo' leak");
    216  is(record.leaks[1].count, 1, "We leak one object on this file");
    217  is(record.leaks[1].lines.length, 1, "We leak from only one line");
    218  is(record.leaks[1].lines[0], "1: 1", "On first line, we leak one object");
    219  tracker.stop();
    220 
    221  TrackedObjects.clear();
    222 });
    223 
    224 add_task(async function () {
    225  info("Test that transient globals are not leaked");
    226 
    227  const tracker = allocationTracker({ watchAllGlobals: true });
    228 
    229  let sandboxBefore = Cu.Sandbox(window);
    230  // We need to allocate at least one object from the global to reproduce the leak
    231  Cu.evalInSandbox(
    232    "this.foo = {};",
    233    sandboxBefore,
    234    null,
    235    "sandbox-before.js",
    236    1
    237  );
    238  const weakBefore = Cu.getWeakReference(sandboxBefore);
    239  sandboxBefore = null;
    240 
    241  await tracker.startRecordingAllocations();
    242 
    243  ok(
    244    !weakBefore.get(),
    245    "Sandbox created before the record should have been freed by GCs done by startRecordingAllocations"
    246  );
    247 
    248  let sandboxDuring = Cu.Sandbox(window);
    249  // We need to allocate at least one object from the global to reproduce the leak
    250  Cu.evalInSandbox(
    251    "this.bar = {};",
    252    sandboxDuring,
    253    null,
    254    "sandbox-during.js",
    255    1
    256  );
    257  const weakDuring = Cu.getWeakReference(sandboxDuring);
    258  sandboxDuring = null;
    259 
    260  await tracker.stopRecordingAllocations();
    261 
    262  ok(
    263    !weakDuring.get(),
    264    "Sandbox should have been freed by GCs done by stopRecordingAllocations"
    265  );
    266 
    267  tracker.stop();
    268 });