tor-browser

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

commit 873685d76c7ccb6ac59d68a1d973cefa12efe531
parent aced10ade5446349fa02acfc59c0e6345f66b1c1
Author: Alexandre Poirot <poirot.alex@gmail.com>
Date:   Mon,  6 Oct 2025 13:42:47 +0000

Bug 1991713 - [devtools] Improve logging of retaining path logged by the allocation tracker. r=devtools-reviewers,nchevobbe

Differential Revision: https://phabricator.services.mozilla.com/D266859

Diffstat:
Mdevtools/shared/test-helpers/allocation-tracker.js | 170++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
1 file changed, 116 insertions(+), 54 deletions(-)

diff --git a/devtools/shared/test-helpers/allocation-tracker.js b/devtools/shared/test-helpers/allocation-tracker.js @@ -62,7 +62,7 @@ exports.allocationTracker = function ({ watchAllGlobals, watchDevToolsGlobals, } = {}) { - dump("DEVTOOLS ALLOCATION: Start logging allocations\n"); + logTracker("Start logging allocations"); let dbg = new global.Debugger(); // Enable allocation site tracking, to have the stack for each allocation @@ -84,7 +84,7 @@ exports.allocationTracker = function ({ acceptGlobal = g => { // self-hosting-global crashes when trying to call unsafeDereference if (g.class == "self-hosting-global") { - dump("TRACKER NEW GLOBAL: - : " + g.class + "\n"); + logTracker("NEW GLOBAL: - : " + g.class); return false; } let ref = g.unsafeDereference(); @@ -107,9 +107,7 @@ exports.allocationTracker = function ({ accept = false; } - dump( - "TRACKER NEW GLOBAL: " + (accept ? "+" : "-") + " : " + location + "\n" - ); + logTracker("NEW GLOBAL: " + (accept ? "+" : "-") + " : " + location); return accept; }; } @@ -336,12 +334,11 @@ exports.allocationTracker = function ({ }) // Filter out modules where we only freed objects .filter(({ count }) => count > 0); - dump( + logTracker( "DEVTOOLS ALLOCATION: " + message + ":\n" + - JSON.stringify(allocationList, null, 2) + - "\n" + JSON.stringify(allocationList, null, 2) ); return allocationList; }, @@ -413,10 +410,9 @@ exports.allocationTracker = function ({ }, logCount() { - dump( + logTracker( "DEVTOOLS ALLOCATION: Javascript object allocations: " + - this.countAllocations() + - "\n" + this.countAllocations() ); }, @@ -516,47 +512,103 @@ exports.allocationTracker = function ({ } const snapshot = ChromeUtils.readHeapSnapshot(snapshotFile); - function getObjectClass(id) { + function getObjectDescription(id, prefix = 0) { + prefix = " ".repeat(prefix); if (!id) { - return "<null>"; + return prefix + "<null>"; } try { let stack = [...snapshot.describeNode({ by: "allocationStack" }, id)]; - let line; if (stack) { stack = stack.find(([src]) => src != "noStack"); if (stack) { - line = stack[0].line; - stack = stack[0].source; - if (stack) { - const pstack = stack; - stack = stack.match(/\/([^\/]+)$/); - if (stack) { - stack = stack[1]; - } else { - stack = pstack; + const { line, column, source } = stack[0]; + if (source) { + const lines = getFileContent(source); + const lineBefore = lines[line - 2]; + const lineText = lines[line - 1]; + const lineAfter = lines[line]; + const filename = source.substr(source.lastIndexOf("/") + 1); + + stack = "allocated at " + source + ":\n"; + // Print one line before and after for context + if (lineBefore.trim().length) { + stack += prefix + ` ${filename} @ ${line - 1} \u007C`; + stack += "\x1b[2m" + lineBefore + "\n"; + } + stack += prefix + ` ${filename} @ ${line} > \u007C`; + // Grey out the beginning of the line, before frame's column, + // and display an arrow before displaying the rest of the line. + stack += + "\x1b[2m" + + lineText.substr(0, column - 1) + + "\x1b[0m" + + "\u21A6 " + + lineText.substr(column - 1) + + "\n"; + if (lineAfter.trim().length) { + stack += prefix + ` ${filename} @ ${line + 1} \u007C`; + stack += lineAfter; } } else { - stack = "no-source"; + stack = "(missing source)"; } } else { - stack = "no-stack"; + stack = "(without allocation stack)"; } } else { - stack = "no-desc"; + stack = "(without description)"; } - return ( - Object.entries( - snapshot.describeNode({ by: "objectClass" }, id) - )[0][0] + (stack ? "@" + stack + ":" + line : "") - ); + let objectClass = Object.entries( + snapshot.describeNode({ by: "objectClass" }, id) + )[0][0]; + if (objectClass == "other") { + objectClass = Object.entries( + snapshot.describeNode({ by: "internalType" }, id) + )[0][0]; + } + const arrow = prefix > 0 ? "\\--> " : ""; + return prefix + arrow + objectClass + " " + stack; } catch (e) { if (e.name == "NS_ERROR_ILLEGAL_VALUE") { - return "<not-in-memory-snapshot:is-from-untracked-global?>"; + return ( + prefix + "<not-in-memory-snapshot:is-from-untracked-global?>" + ); } - return "<invalid:" + id + ":" + e + ">"; + return prefix + "<invalid:" + id + ":" + e + ">"; } } + + const fileContents = new Map(); + + function getFileContent(url) { + let content = fileContents.get(url); + if (content) { + return content; + } + content = readURI(url).split("\n"); + fileContents.set(url, content); + return content; + } + + function readURI(uri) { + const { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs", + { global: "contextual" } + ); + const stream = NetUtil.newChannel({ + uri: NetUtil.newURI(uri, "UTF-8"), + loadUsingSystemPrincipal: true, + }).open(); + const count = stream.available(); + const data = NetUtil.readInputStreamToString(stream, count, { + charset: "UTF-8", + }); + + stream.close(); + return data; + } + function printPath(src, dst) { let paths; try { @@ -564,39 +616,45 @@ exports.allocationTracker = function ({ } catch (e) {} if (paths && paths.has(dst)) { let pathLength = Infinity; + let n = 0; for (const path of paths.get(dst)) { + n++; // Only print the smaller paths. // The longer ones will only repeat the smaller ones, with some extra edges. - if (path.length > pathLength) { + if (path.length > pathLength + 1) { continue; } pathLength = path.length; - dump( - "- " + + logTracker( + `Path #${n}:\n` + path - .map( - ({ predecessor, edge }) => - getObjectClass(predecessor) + "." + edge - ) - .join("\n \\--> ") + - "\n \\--> " + - getObjectClass(dst) + - "\n" + .map(({ predecessor, edge }, i) => { + return ( + getObjectDescription(predecessor, i) + + "\n" + + " ".repeat(i) + + "Holds the following object via '" + + edge + + "' attribute:\n" + ); + }) + .join("") + + getObjectDescription(dst, path.length) ); } } else { - dump("NO-PATH\n"); + logTracker("NO-PATH"); } } const tree = snapshot.computeDominatorTree(); for (const objectNodeId of objects) { - dump(" # Tracing: " + getObjectClass(objectNodeId) + "\n"); + logTracker(" # Tracing object #" + objectNodeId + "\n"); // Print the path from the global object down to leaked object. // This print the allocation site of each object which has a reference // to another object, ultimately leading to our leaked object. - dump("### Path(s) from root:\n"); + logTracker("### Path(s) from root:"); printPath(tree.root, objectNodeId); /** @@ -605,11 +663,11 @@ exports.allocationTracker = function ({ // Print the dominators. // i.e. from the leaked object, print all parent objects whichs // keeps a reference to the previous object, up to a global object. - dump("### Dominators:\n"); + logTracker("### Dominators:"); let node = objectNodeId, - dump(" " + getObjectClass(node) + "\n"); + logTracker(" " + getObjectDescription(node)); while ((node = tree.getImmediateDominator(node))) { - dump(" ^-- " + getObjectClass(node) + "\n"); + logTracker(" ^-- " + getObjectDescription(node)); } */ @@ -618,21 +676,25 @@ exports.allocationTracker = function ({ * This will print all what it keeps allocated, * kinds of list of attributes * - dump("### Dominateds:\n"); + logTracker("### Dominateds:"); node = objectNodeId, - dump(" " + getObjectClass(node) + "\n"); + logTracker(" " + getObjectDescription(node)); for (const n of tree.getImmediatelyDominated(objectNodeId)) { - dump(" --> " + getObjectClass(n) + "\n"); + logTracker(" --> " + getObjectDescription(n)); } */ } }, stop() { - dump("DEVTOOLS ALLOCATION: Stop logging allocations\n"); + logTracker("Stop logging allocations"); dbg.onNewGlobalObject = undefined; dbg.removeAllDebuggees(); dbg = null; }, }; }; + +function logTracker(message) { + dump(` \x1b[2m[TRACKER]\x1b[0m ${message}\n`); +}