Files
Florian Quèze 7b92246a72 Bug 2020003 - Add weak map edge support to nsICycleCollectorHandler. r=mccr8,devtools-reviewers,ochameau
nsICycleCollectorHandler now exposes noteWeakMapEntry, allowing JS
consumers to receive weak map entries from the CC graph. This enables
ShutdownLeakPathFinder and CCAnalyzer to find retention paths that
pass through weak maps.

Differential Revision: https://phabricator.services.mozilla.com/D285345
2026-03-17 00:23:58 +00:00

166 lines
4.4 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import { setTimeout } from "resource://gre/modules/Timer.sys.mjs";
/**
* Helper class to retrieve CC/GC Logs via nsICycleCollectorListener interface.
*/
export class CCAnalyzer {
clear() {
this.processingCount = 0;
this.graph = {};
this.roots = [];
this.garbage = [];
this.edges = [];
this.listener = null;
this.count = 0;
}
/**
* Run the analyzer by running the CC/GC, which would allow use
* to call nsICycleCollectorListener.processNext()
* which would call nsICycleCollectorListener.{noteRefCountedObject,noteGCedObject,noteEdge}.
*
* @param {boolean} wantAllTraces
* See nsICycleCollectorListener.allTraces() jsdoc.
*/
async run(wantAllTraces = false) {
this.clear();
// Instantiate and configure the CC logger
this.listener = Cu.createCCLogger();
if (wantAllTraces) {
dump("CC Analyzer >> all traces!\n");
this.listener = this.listener.allTraces();
}
this.listener.disableLog = true;
this.listener.wantAfterProcessing = true;
// Register the CC logger
Cu.forceCC(this.listener);
// Process the entire heap step by step in 10K chunks
let done = false;
while (!done) {
for (let i = 0; i < 10000; i++) {
if (!this.listener.processNext(this)) {
done = true;
break;
}
}
dump("Process CC/GC logs " + this.count + "\n");
// Process next chunk after an event loop to avoid freezing the process
await new Promise(resolve => setTimeout(resolve, 0));
}
await new Promise(resolve => setTimeout(resolve, 0));
dump("Done!\n");
}
noteRefCountedObject(address, refCount, objectDescription) {
const o = this.ensureObject(address);
o.address = address;
o.refcount = refCount;
o.name = objectDescription;
}
noteGCedObject(address, marked, objectDescription, compartmentAddr) {
const o = this.ensureObject(address);
o.address = address;
o.gcmarked = marked;
o.name = objectDescription;
o.compartment = compartmentAddr;
}
noteEdge(fromAddress, toAddress, edgeName) {
const fromObject = this.ensureObject(fromAddress);
const toObject = this.ensureObject(toAddress);
fromObject.edges.push({ name: edgeName, to: toObject });
toObject.owners.push({ name: edgeName, from: fromObject });
this.edges.push({
name: edgeName,
from: fromObject,
to: toObject,
});
}
noteWeakMapEntry(mapAddr, keyAddr, keyDelegateAddr, valueAddr) {
if (valueAddr == "0x0") {
return;
}
// As an approximation, we only record the edge from the key to the value,
// ignoring the path to the map and key delegates.
const fromAddr = keyAddr != "0x0" ? keyAddr : mapAddr;
if (fromAddr == "0x0") {
return;
}
const fromObject = this.ensureObject(fromAddr);
const toObject = this.ensureObject(valueAddr);
const edgeName = "WeakMap value via key " + keyAddr;
fromObject.edges.push({ name: edgeName, to: toObject });
toObject.owners.push({ name: edgeName, from: fromObject });
this.edges.push({ name: edgeName, from: fromObject, to: toObject });
}
describeRoot(address, knownEdges) {
const o = this.ensureObject(address);
o.root = true;
o.knownEdges = knownEdges;
this.roots.push(o);
}
describeGarbage(address) {
const o = this.ensureObject(address);
o.garbage = true;
this.garbage.push(o);
}
ensureObject(address) {
if (!this.graph[address]) {
this.count++;
this.graph[address] = new CCObject();
}
return this.graph[address];
}
find(text) {
const result = [];
for (const address in this.graph) {
const o = this.graph[address];
if (!o.garbage && o.name.includes(text)) {
result.push(o);
}
}
return result;
}
findNotJS() {
const result = [];
for (const address in this.graph) {
const o = this.graph[address];
if (!o.garbage && o.name.indexOf("JS") != 0) {
result.push(o);
}
}
return result;
}
}
class CCObject {
constructor() {
this.name = "";
this.address = null;
this.refcount = 0;
this.gcmarked = false;
this.root = false;
this.garbage = false;
this.knownEdges = 0;
this.edges = [];
this.owners = [];
}
}