tor-browser

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

leakhunt.js (3878B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 /**
      8 * Memory leak hunter. Walks a tree of objects looking for DOM nodes.
      9 * Usage:
     10 * leakHunt({
     11 *   thing: thing,
     12 *   otherthing: otherthing
     13 * });
     14 */
     15 function leakHunt(root) {
     16  const path = [];
     17  const seen = [];
     18 
     19  try {
     20    const output = leakHunt.inner(root, path, seen);
     21    output.forEach(function (line) {
     22      dump(line + "\n");
     23    });
     24  } catch (ex) {
     25    dump(ex + "\n");
     26  }
     27 }
     28 
     29 leakHunt.inner = function (root, path, seen) {
     30  const prefix = new Array(path.length).join("  ");
     31 
     32  const reply = [];
     33  function log(msg) {
     34    reply.push(msg);
     35  }
     36 
     37  let direct;
     38  try {
     39    direct = Object.keys(root);
     40  } catch (ex) {
     41    log(prefix + "  Error enumerating: " + ex);
     42    return reply;
     43  }
     44 
     45  try {
     46    let index = 0;
     47    for (const data of root) {
     48      const prop = "" + index;
     49      leakHunt.digProperty(prop, data, path, seen, direct, log);
     50      index++;
     51    }
     52  } catch (ex) {
     53    /* Ignore things that are not enumerable */
     54  }
     55 
     56  for (const prop in root) {
     57    let data;
     58    try {
     59      data = root[prop];
     60    } catch (ex) {
     61      log(prefix + "  " + prop + " = Error: " + ex.toString().substring(0, 30));
     62      continue;
     63    }
     64 
     65    leakHunt.digProperty(prop, data, path, seen, direct, log);
     66  }
     67 
     68  return reply;
     69 };
     70 
     71 leakHunt.hide = [/^string$/, /^number$/, /^boolean$/, /^null/, /^undefined/];
     72 
     73 leakHunt.noRecurse = [
     74  /^string$/,
     75  /^number$/,
     76  /^boolean$/,
     77  /^null/,
     78  /^undefined/,
     79  /^Window$/,
     80  /^Document$/,
     81  /^XULElement$/,
     82  /^DOMWindow$/,
     83  /^HTMLDocument$/,
     84  /^HTML.*Element$/,
     85  /^ChromeWindow$/,
     86 ];
     87 
     88 leakHunt.digProperty = function (prop, data, path, seen, direct, log) {
     89  const newPath = path.slice();
     90  newPath.push(prop);
     91  const prefix = new Array(newPath.length).join("  ");
     92 
     93  let recurse = true;
     94  let message = leakHunt.getType(data);
     95 
     96  if (leakHunt.matchesAnyPattern(message, leakHunt.hide)) {
     97    return;
     98  }
     99 
    100  if (message === "function" && !direct.includes(prop)) {
    101    return;
    102  }
    103 
    104  if (message === "string") {
    105    const extra = data.length > 10 ? data.substring(0, 9) + "_" : data;
    106    message += ' "' + extra.replace(/\n/g, "|") + '"';
    107    recurse = false;
    108  } else if (leakHunt.matchesAnyPattern(message, leakHunt.noRecurse)) {
    109    message += " (no recurse)";
    110    recurse = false;
    111  } else if (seen.includes(data)) {
    112    message += " (already seen)";
    113    recurse = false;
    114  }
    115 
    116  if (recurse) {
    117    seen.push(data);
    118    const lines = leakHunt.inner(data, newPath, seen);
    119    if (!lines.length) {
    120      if (message !== "function") {
    121        log(prefix + prop + " = " + message + " { }");
    122      }
    123    } else {
    124      log(prefix + prop + " = " + message + " {");
    125      lines.forEach(function (line) {
    126        log(line);
    127      });
    128      log(prefix + "}");
    129    }
    130  } else {
    131    log(prefix + prop + " = " + message);
    132  }
    133 };
    134 
    135 leakHunt.matchesAnyPattern = function (str, patterns) {
    136  let match = false;
    137  patterns.forEach(function (pattern) {
    138    if (str.match(pattern)) {
    139      match = true;
    140    }
    141  });
    142  return match;
    143 };
    144 
    145 leakHunt.getType = function (data) {
    146  if (data === null) {
    147    return "null";
    148  }
    149  if (data === undefined) {
    150    return "undefined";
    151  }
    152 
    153  let type = typeof data;
    154  if (type === "object" || type === "Object") {
    155    type = leakHunt.getCtorName(data);
    156  }
    157 
    158  return type;
    159 };
    160 
    161 leakHunt.getCtorName = function (obj) {
    162  try {
    163    if (obj.constructor && obj.constructor.name) {
    164      return obj.constructor.name;
    165    }
    166  } catch (ex) {
    167    return "UnknownObject";
    168  }
    169 
    170  // If that fails, use Objects toString which sometimes gives something
    171  // better than 'Object', and at least defaults to Object if nothing better
    172  return Object.prototype.toString.call(obj).slice(8, -1);
    173 };