UNPKG

@dcloudio/uni-debugger

Version:

uni-app debugger

1,683 lines (1,509 loc) 98.9 kB
/* * Copyright (C) 2011 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @interface */ HeapSnapshotWorker.HeapSnapshotItem = function() {}; HeapSnapshotWorker.HeapSnapshotItem.prototype = { /** * @return {number} */ itemIndex() {}, /** * @return {!Object} */ serialize() {} }; /** * @implements {HeapSnapshotWorker.HeapSnapshotItem} * @unrestricted */ HeapSnapshotWorker.HeapSnapshotEdge = class { /** * @param {!HeapSnapshotWorker.HeapSnapshot} snapshot * @param {number=} edgeIndex */ constructor(snapshot, edgeIndex) { this._snapshot = snapshot; this._edges = snapshot.containmentEdges; this.edgeIndex = edgeIndex || 0; } /** * @return {!HeapSnapshotWorker.HeapSnapshotEdge} */ clone() { return new HeapSnapshotWorker.HeapSnapshotEdge(this._snapshot, this.edgeIndex); } /** * @return {boolean} */ hasStringName() { throw new Error('Not implemented'); } /** * @return {string} */ name() { throw new Error('Not implemented'); } /** * @return {!HeapSnapshotWorker.HeapSnapshotNode} */ node() { return this._snapshot.createNode(this.nodeIndex()); } /** * @return {number} */ nodeIndex() { return this._edges[this.edgeIndex + this._snapshot._edgeToNodeOffset]; } /** * @override * @return {string} */ toString() { return 'HeapSnapshotEdge: ' + this.name(); } /** * @return {string} */ type() { return this._snapshot._edgeTypes[this.rawType()]; } /** * @override * @return {number} */ itemIndex() { return this.edgeIndex; } /** * @override * @return {!HeapSnapshotModel.Edge} */ serialize() { return new HeapSnapshotModel.Edge(this.name(), this.node().serialize(), this.type(), this.edgeIndex); } /** * @protected * @return {number} */ rawType() { return this._edges[this.edgeIndex + this._snapshot._edgeTypeOffset]; } }; /** * @interface */ HeapSnapshotWorker.HeapSnapshotItemIterator = function() {}; HeapSnapshotWorker.HeapSnapshotItemIterator.prototype = { /** * @return {boolean} */ hasNext() {}, /** * @return {!HeapSnapshotWorker.HeapSnapshotItem} */ item() {}, next() {} }; /** * @interface */ HeapSnapshotWorker.HeapSnapshotItemIndexProvider = function() {}; HeapSnapshotWorker.HeapSnapshotItemIndexProvider.prototype = { /** * @param {number} newIndex * @return {!HeapSnapshotWorker.HeapSnapshotItem} */ itemForIndex(newIndex) {}, }; /** * @implements {HeapSnapshotWorker.HeapSnapshotItemIndexProvider} * @unrestricted */ HeapSnapshotWorker.HeapSnapshotNodeIndexProvider = class { /** * @param {!HeapSnapshotWorker.HeapSnapshot} snapshot */ constructor(snapshot) { this._node = snapshot.createNode(); } /** * @override * @param {number} index * @return {!HeapSnapshotWorker.HeapSnapshotNode} */ itemForIndex(index) { this._node.nodeIndex = index; return this._node; } }; /** * @implements {HeapSnapshotWorker.HeapSnapshotItemIndexProvider} * @unrestricted */ HeapSnapshotWorker.HeapSnapshotEdgeIndexProvider = class { /** * @param {!HeapSnapshotWorker.HeapSnapshot} snapshot */ constructor(snapshot) { this._edge = snapshot.createEdge(0); } /** * @override * @param {number} index * @return {!HeapSnapshotWorker.HeapSnapshotEdge} */ itemForIndex(index) { this._edge.edgeIndex = index; return this._edge; } }; /** * @implements {HeapSnapshotWorker.HeapSnapshotItemIndexProvider} * @unrestricted */ HeapSnapshotWorker.HeapSnapshotRetainerEdgeIndexProvider = class { /** * @param {!HeapSnapshotWorker.HeapSnapshot} snapshot */ constructor(snapshot) { this._retainerEdge = snapshot.createRetainingEdge(0); } /** * @override * @param {number} index * @return {!HeapSnapshotWorker.HeapSnapshotRetainerEdge} */ itemForIndex(index) { this._retainerEdge.setRetainerIndex(index); return this._retainerEdge; } }; /** * @implements {HeapSnapshotWorker.HeapSnapshotItemIterator} * @unrestricted */ HeapSnapshotWorker.HeapSnapshotEdgeIterator = class { /** * @param {!HeapSnapshotWorker.HeapSnapshotNode} node */ constructor(node) { this._sourceNode = node; this.edge = node._snapshot.createEdge(node.edgeIndexesStart()); } /** * @override * @return {boolean} */ hasNext() { return this.edge.edgeIndex < this._sourceNode.edgeIndexesEnd(); } /** * @override * @return {!HeapSnapshotWorker.HeapSnapshotEdge} */ item() { return this.edge; } /** * @override */ next() { this.edge.edgeIndex += this.edge._snapshot._edgeFieldsCount; } }; /** * @implements {HeapSnapshotWorker.HeapSnapshotItem} * @unrestricted */ HeapSnapshotWorker.HeapSnapshotRetainerEdge = class { /** * @param {!HeapSnapshotWorker.HeapSnapshot} snapshot * @param {number} retainerIndex */ constructor(snapshot, retainerIndex) { this._snapshot = snapshot; this.setRetainerIndex(retainerIndex); } /** * @return {!HeapSnapshotWorker.HeapSnapshotRetainerEdge} */ clone() { return new HeapSnapshotWorker.HeapSnapshotRetainerEdge(this._snapshot, this.retainerIndex()); } /** * @return {boolean} */ hasStringName() { return this._edge().hasStringName(); } /** * @return {string} */ name() { return this._edge().name(); } /** * @return {!HeapSnapshotWorker.HeapSnapshotNode} */ node() { return this._node(); } /** * @return {number} */ nodeIndex() { return this._retainingNodeIndex; } /** * @return {number} */ retainerIndex() { return this._retainerIndex; } /** * @param {number} retainerIndex */ setRetainerIndex(retainerIndex) { if (retainerIndex === this._retainerIndex) return; this._retainerIndex = retainerIndex; this._globalEdgeIndex = this._snapshot._retainingEdges[retainerIndex]; this._retainingNodeIndex = this._snapshot._retainingNodes[retainerIndex]; this._edgeInstance = null; this._nodeInstance = null; } /** * @param {number} edgeIndex */ set edgeIndex(edgeIndex) { this.setRetainerIndex(edgeIndex); } _node() { if (!this._nodeInstance) this._nodeInstance = this._snapshot.createNode(this._retainingNodeIndex); return this._nodeInstance; } _edge() { if (!this._edgeInstance) this._edgeInstance = this._snapshot.createEdge(this._globalEdgeIndex); return this._edgeInstance; } /** * @override * @return {string} */ toString() { return this._edge().toString(); } /** * @override * @return {number} */ itemIndex() { return this._retainerIndex; } /** * @override * @return {!HeapSnapshotModel.Edge} */ serialize() { return new HeapSnapshotModel.Edge(this.name(), this.node().serialize(), this.type(), this._globalEdgeIndex); } /** * @return {string} */ type() { return this._edge().type(); } }; /** * @implements {HeapSnapshotWorker.HeapSnapshotItemIterator} * @unrestricted */ HeapSnapshotWorker.HeapSnapshotRetainerEdgeIterator = class { /** * @param {!HeapSnapshotWorker.HeapSnapshotNode} retainedNode */ constructor(retainedNode) { const snapshot = retainedNode._snapshot; const retainedNodeOrdinal = retainedNode.ordinal(); const retainerIndex = snapshot._firstRetainerIndex[retainedNodeOrdinal]; this._retainersEnd = snapshot._firstRetainerIndex[retainedNodeOrdinal + 1]; this.retainer = snapshot.createRetainingEdge(retainerIndex); } /** * @override * @return {boolean} */ hasNext() { return this.retainer.retainerIndex() < this._retainersEnd; } /** * @override * @return {!HeapSnapshotWorker.HeapSnapshotRetainerEdge} */ item() { return this.retainer; } /** * @override */ next() { this.retainer.setRetainerIndex(this.retainer.retainerIndex() + 1); } }; /** * @implements {HeapSnapshotWorker.HeapSnapshotItem} * @unrestricted */ HeapSnapshotWorker.HeapSnapshotNode = class { /** * @param {!HeapSnapshotWorker.HeapSnapshot} snapshot * @param {number=} nodeIndex */ constructor(snapshot, nodeIndex) { this._snapshot = snapshot; this.nodeIndex = nodeIndex || 0; } /** * @return {number} */ distance() { return this._snapshot._nodeDistances[this.nodeIndex / this._snapshot._nodeFieldCount]; } /** * @return {string} */ className() { throw new Error('Not implemented'); } /** * @return {number} */ classIndex() { throw new Error('Not implemented'); } /** * @return {number} */ dominatorIndex() { const nodeFieldCount = this._snapshot._nodeFieldCount; return this._snapshot._dominatorsTree[this.nodeIndex / this._snapshot._nodeFieldCount] * nodeFieldCount; } /** * @return {!HeapSnapshotWorker.HeapSnapshotEdgeIterator} */ edges() { return new HeapSnapshotWorker.HeapSnapshotEdgeIterator(this); } /** * @return {number} */ edgesCount() { return (this.edgeIndexesEnd() - this.edgeIndexesStart()) / this._snapshot._edgeFieldsCount; } /** * @return {number} */ id() { throw new Error('Not implemented'); } /** * @return {boolean} */ isRoot() { return this.nodeIndex === this._snapshot._rootNodeIndex; } /** * @return {string} */ name() { return this._snapshot.strings[this._name()]; } /** * @return {number} */ retainedSize() { return this._snapshot._retainedSizes[this.ordinal()]; } /** * @return {!HeapSnapshotWorker.HeapSnapshotRetainerEdgeIterator} */ retainers() { return new HeapSnapshotWorker.HeapSnapshotRetainerEdgeIterator(this); } /** * @return {number} */ retainersCount() { const snapshot = this._snapshot; const ordinal = this.ordinal(); return snapshot._firstRetainerIndex[ordinal + 1] - snapshot._firstRetainerIndex[ordinal]; } /** * @return {number} */ selfSize() { const snapshot = this._snapshot; return snapshot.nodes[this.nodeIndex + snapshot._nodeSelfSizeOffset]; } /** * @return {string} */ type() { return this._snapshot._nodeTypes[this.rawType()]; } /** * @return {number} */ traceNodeId() { const snapshot = this._snapshot; return snapshot.nodes[this.nodeIndex + snapshot._nodeTraceNodeIdOffset]; } /** * @override * @return {number} */ itemIndex() { return this.nodeIndex; } /** * @override * @return {!HeapSnapshotModel.Node} */ serialize() { return new HeapSnapshotModel.Node( this.id(), this.name(), this.distance(), this.nodeIndex, this.retainedSize(), this.selfSize(), this.type()); } /** * @return {number} */ _name() { const snapshot = this._snapshot; return snapshot.nodes[this.nodeIndex + snapshot._nodeNameOffset]; } /** * @return {number} */ edgeIndexesStart() { return this._snapshot._firstEdgeIndexes[this.ordinal()]; } /** * @return {number} */ edgeIndexesEnd() { return this._snapshot._firstEdgeIndexes[this.ordinal() + 1]; } /** * @return {number} */ ordinal() { return this.nodeIndex / this._snapshot._nodeFieldCount; } /** * @return {number} */ _nextNodeIndex() { return this.nodeIndex + this._snapshot._nodeFieldCount; } /** * @protected * @return {number} */ rawType() { const snapshot = this._snapshot; return snapshot.nodes[this.nodeIndex + snapshot._nodeTypeOffset]; } }; /** * @implements {HeapSnapshotWorker.HeapSnapshotItemIterator} * @unrestricted */ HeapSnapshotWorker.HeapSnapshotNodeIterator = class { /** * @param {!HeapSnapshotWorker.HeapSnapshotNode} node */ constructor(node) { this.node = node; this._nodesLength = node._snapshot.nodes.length; } /** * @override * @return {boolean} */ hasNext() { return this.node.nodeIndex < this._nodesLength; } /** * @override * @return {!HeapSnapshotWorker.HeapSnapshotNode} */ item() { return this.node; } /** * @override */ next() { this.node.nodeIndex = this.node._nextNodeIndex(); } }; /** * @implements {HeapSnapshotWorker.HeapSnapshotItemIterator} * @unrestricted */ HeapSnapshotWorker.HeapSnapshotIndexRangeIterator = class { /** * @param {!HeapSnapshotWorker.HeapSnapshotItemIndexProvider} itemProvider * @param {!Array.<number>|!Uint32Array} indexes */ constructor(itemProvider, indexes) { this._itemProvider = itemProvider; this._indexes = indexes; this._position = 0; } /** * @override * @return {boolean} */ hasNext() { return this._position < this._indexes.length; } /** * @override * @return {!HeapSnapshotWorker.HeapSnapshotItem} */ item() { const index = this._indexes[this._position]; return this._itemProvider.itemForIndex(index); } /** * @override */ next() { ++this._position; } }; /** * @implements {HeapSnapshotWorker.HeapSnapshotItemIterator} * @unrestricted */ HeapSnapshotWorker.HeapSnapshotFilteredIterator = class { /** * @param {!HeapSnapshotWorker.HeapSnapshotItemIterator} iterator * @param {function(!HeapSnapshotWorker.HeapSnapshotItem):boolean=} filter */ constructor(iterator, filter) { this._iterator = iterator; this._filter = filter; this._skipFilteredItems(); } /** * @override * @return {boolean} */ hasNext() { return this._iterator.hasNext(); } /** * @override * @return {!HeapSnapshotWorker.HeapSnapshotItem} */ item() { return this._iterator.item(); } /** * @override */ next() { this._iterator.next(); this._skipFilteredItems(); } _skipFilteredItems() { while (this._iterator.hasNext() && !this._filter(this._iterator.item())) this._iterator.next(); } }; /** * @unrestricted */ HeapSnapshotWorker.HeapSnapshotProgress = class { /** * @param {!HeapSnapshotWorker.HeapSnapshotWorkerDispatcher=} dispatcher */ constructor(dispatcher) { this._dispatcher = dispatcher; } /** * @param {string} status */ updateStatus(status) { this._sendUpdateEvent(Common.UIString(status)); } /** * @param {string} title * @param {number} value * @param {number} total */ updateProgress(title, value, total) { const percentValue = ((total ? (value / total) : 0) * 100).toFixed(0); this._sendUpdateEvent(Common.UIString(title, percentValue)); } /** * @param {string} error */ reportProblem(error) { // May be undefined in tests. if (this._dispatcher) this._dispatcher.sendEvent(HeapSnapshotModel.HeapSnapshotProgressEvent.BrokenSnapshot, error); } /** * @param {string} text */ _sendUpdateEvent(text) { // May be undefined in tests. if (this._dispatcher) this._dispatcher.sendEvent(HeapSnapshotModel.HeapSnapshotProgressEvent.Update, text); } }; /** * @unrestricted */ HeapSnapshotWorker.HeapSnapshotProblemReport = class { /** * @param {string} title */ constructor(title) { this._errors = [title]; } /** * @param {string} error */ addError(error) { if (this._errors.length > 100) return; this._errors.push(error); } /** * @override * @return {string} */ toString() { return this._errors.join('\n '); } }; /** * @unrestricted */ HeapSnapshotWorker.HeapSnapshot = class { /** * @param {!Object} profile * @param {!HeapSnapshotWorker.HeapSnapshotProgress} progress */ constructor(profile, progress) { /** @type {!Uint32Array} */ this.nodes = profile.nodes; /** @type {!Uint32Array} */ this.containmentEdges = profile.edges; /** @type {!HeapSnapshotMetainfo} */ this._metaNode = profile.snapshot.meta; /** @type {!Array.<number>} */ this._rawSamples = profile.samples; /** @type {?HeapSnapshotModel.Samples} */ this._samples = null; /** @type {!Array.<string>} */ this.strings = profile.strings; this._progress = progress; this._noDistance = -5; this._rootNodeIndex = 0; if (profile.snapshot.root_index) this._rootNodeIndex = profile.snapshot.root_index; this._snapshotDiffs = {}; this._aggregatesForDiff = null; this._aggregates = {}; this._aggregatesSortedFlags = {}; this._profile = profile; } /** * @protected */ initialize() { const meta = this._metaNode; this._nodeTypeOffset = meta.node_fields.indexOf('type'); this._nodeNameOffset = meta.node_fields.indexOf('name'); this._nodeIdOffset = meta.node_fields.indexOf('id'); this._nodeSelfSizeOffset = meta.node_fields.indexOf('self_size'); this._nodeEdgeCountOffset = meta.node_fields.indexOf('edge_count'); this._nodeTraceNodeIdOffset = meta.node_fields.indexOf('trace_node_id'); this._nodeFieldCount = meta.node_fields.length; this._nodeTypes = meta.node_types[this._nodeTypeOffset]; this._nodeArrayType = this._nodeTypes.indexOf('array'); this._nodeHiddenType = this._nodeTypes.indexOf('hidden'); this._nodeObjectType = this._nodeTypes.indexOf('object'); this._nodeNativeType = this._nodeTypes.indexOf('native'); this._nodeConsStringType = this._nodeTypes.indexOf('concatenated string'); this._nodeSlicedStringType = this._nodeTypes.indexOf('sliced string'); this._nodeCodeType = this._nodeTypes.indexOf('code'); this._nodeSyntheticType = this._nodeTypes.indexOf('synthetic'); this._edgeFieldsCount = meta.edge_fields.length; this._edgeTypeOffset = meta.edge_fields.indexOf('type'); this._edgeNameOffset = meta.edge_fields.indexOf('name_or_index'); this._edgeToNodeOffset = meta.edge_fields.indexOf('to_node'); this._edgeTypes = meta.edge_types[this._edgeTypeOffset]; this._edgeTypes.push('invisible'); this._edgeElementType = this._edgeTypes.indexOf('element'); this._edgeHiddenType = this._edgeTypes.indexOf('hidden'); this._edgeInternalType = this._edgeTypes.indexOf('internal'); this._edgeShortcutType = this._edgeTypes.indexOf('shortcut'); this._edgeWeakType = this._edgeTypes.indexOf('weak'); this._edgeInvisibleType = this._edgeTypes.indexOf('invisible'); this.nodeCount = this.nodes.length / this._nodeFieldCount; this._edgeCount = this.containmentEdges.length / this._edgeFieldsCount; this._retainedSizes = new Float64Array(this.nodeCount); this._firstEdgeIndexes = new Uint32Array(this.nodeCount + 1); this._retainingNodes = new Uint32Array(this._edgeCount); this._retainingEdges = new Uint32Array(this._edgeCount); this._firstRetainerIndex = new Uint32Array(this.nodeCount + 1); this._nodeDistances = new Int32Array(this.nodeCount); this._firstDominatedNodeIndex = new Uint32Array(this.nodeCount + 1); this._dominatedNodes = new Uint32Array(this.nodeCount - 1); this._progress.updateStatus('Building edge indexes\u2026'); this._buildEdgeIndexes(); this._progress.updateStatus('Building retainers\u2026'); this._buildRetainers(); this._progress.updateStatus('Calculating node flags\u2026'); this.calculateFlags(); this._progress.updateStatus('Calculating distances\u2026'); this.calculateDistances(); this._progress.updateStatus('Building postorder index\u2026'); const result = this._buildPostOrderIndex(); // Actually it is array that maps node ordinal number to dominator node ordinal number. this._progress.updateStatus('Building dominator tree\u2026'); this._dominatorsTree = this._buildDominatorTree(result.postOrderIndex2NodeOrdinal, result.nodeOrdinal2PostOrderIndex); this._progress.updateStatus('Calculating retained sizes\u2026'); this._calculateRetainedSizes(result.postOrderIndex2NodeOrdinal); this._progress.updateStatus('Building dominated nodes\u2026'); this._buildDominatedNodes(); this._progress.updateStatus('Calculating statistics\u2026'); this.calculateStatistics(); this._progress.updateStatus('Calculating samples\u2026'); this._buildSamples(); this._progress.updateStatus('Finished processing.'); if (this._profile.snapshot.trace_function_count) { this._progress.updateStatus('Building allocation statistics\u2026'); const nodes = this.nodes; const nodesLength = nodes.length; const nodeFieldCount = this._nodeFieldCount; const node = this.rootNode(); const liveObjects = {}; for (let nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) { node.nodeIndex = nodeIndex; const traceNodeId = node.traceNodeId(); let stats = liveObjects[traceNodeId]; if (!stats) liveObjects[traceNodeId] = stats = {count: 0, size: 0, ids: []}; stats.count++; stats.size += node.selfSize(); stats.ids.push(node.id()); } this._allocationProfile = new HeapSnapshotWorker.AllocationProfile(this._profile, liveObjects); this._progress.updateStatus('Done'); } } _buildEdgeIndexes() { const nodes = this.nodes; const nodeCount = this.nodeCount; const firstEdgeIndexes = this._firstEdgeIndexes; const nodeFieldCount = this._nodeFieldCount; const edgeFieldsCount = this._edgeFieldsCount; const nodeEdgeCountOffset = this._nodeEdgeCountOffset; firstEdgeIndexes[nodeCount] = this.containmentEdges.length; for (let nodeOrdinal = 0, edgeIndex = 0; nodeOrdinal < nodeCount; ++nodeOrdinal) { firstEdgeIndexes[nodeOrdinal] = edgeIndex; edgeIndex += nodes[nodeOrdinal * nodeFieldCount + nodeEdgeCountOffset] * edgeFieldsCount; } } _buildRetainers() { const retainingNodes = this._retainingNodes; const retainingEdges = this._retainingEdges; // Index of the first retainer in the _retainingNodes and _retainingEdges // arrays. Addressed by retained node index. const firstRetainerIndex = this._firstRetainerIndex; const containmentEdges = this.containmentEdges; const edgeFieldsCount = this._edgeFieldsCount; const nodeFieldCount = this._nodeFieldCount; const edgeToNodeOffset = this._edgeToNodeOffset; const firstEdgeIndexes = this._firstEdgeIndexes; const nodeCount = this.nodeCount; for (let toNodeFieldIndex = edgeToNodeOffset, l = containmentEdges.length; toNodeFieldIndex < l; toNodeFieldIndex += edgeFieldsCount) { const toNodeIndex = containmentEdges[toNodeFieldIndex]; if (toNodeIndex % nodeFieldCount) throw new Error('Invalid toNodeIndex ' + toNodeIndex); ++firstRetainerIndex[toNodeIndex / nodeFieldCount]; } for (let i = 0, firstUnusedRetainerSlot = 0; i < nodeCount; i++) { const retainersCount = firstRetainerIndex[i]; firstRetainerIndex[i] = firstUnusedRetainerSlot; retainingNodes[firstUnusedRetainerSlot] = retainersCount; firstUnusedRetainerSlot += retainersCount; } firstRetainerIndex[nodeCount] = retainingNodes.length; let nextNodeFirstEdgeIndex = firstEdgeIndexes[0]; for (let srcNodeOrdinal = 0; srcNodeOrdinal < nodeCount; ++srcNodeOrdinal) { const firstEdgeIndex = nextNodeFirstEdgeIndex; nextNodeFirstEdgeIndex = firstEdgeIndexes[srcNodeOrdinal + 1]; const srcNodeIndex = srcNodeOrdinal * nodeFieldCount; for (let edgeIndex = firstEdgeIndex; edgeIndex < nextNodeFirstEdgeIndex; edgeIndex += edgeFieldsCount) { const toNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; if (toNodeIndex % nodeFieldCount) throw new Error('Invalid toNodeIndex ' + toNodeIndex); const firstRetainerSlotIndex = firstRetainerIndex[toNodeIndex / nodeFieldCount]; const nextUnusedRetainerSlotIndex = firstRetainerSlotIndex + (--retainingNodes[firstRetainerSlotIndex]); retainingNodes[nextUnusedRetainerSlotIndex] = srcNodeIndex; retainingEdges[nextUnusedRetainerSlotIndex] = edgeIndex; } } } /** * @param {number=} nodeIndex */ createNode(nodeIndex) { throw new Error('Not implemented'); } /** * @param {number} edgeIndex * @return {!HeapSnapshotWorker.JSHeapSnapshotEdge} */ createEdge(edgeIndex) { throw new Error('Not implemented'); } /** * @param {number} retainerIndex * @return {!HeapSnapshotWorker.JSHeapSnapshotRetainerEdge} */ createRetainingEdge(retainerIndex) { throw new Error('Not implemented'); } /** * @return {!HeapSnapshotWorker.HeapSnapshotNodeIterator} */ _allNodes() { return new HeapSnapshotWorker.HeapSnapshotNodeIterator(this.rootNode()); } /** * @return {!HeapSnapshotWorker.HeapSnapshotNode} */ rootNode() { return this.createNode(this._rootNodeIndex); } /** * @return {number} */ get rootNodeIndex() { return this._rootNodeIndex; } /** * @return {number} */ get totalSize() { return this.rootNode().retainedSize(); } /** * @param {number} nodeIndex * @return {number} */ _getDominatedIndex(nodeIndex) { if (nodeIndex % this._nodeFieldCount) throw new Error('Invalid nodeIndex: ' + nodeIndex); return this._firstDominatedNodeIndex[nodeIndex / this._nodeFieldCount]; } /** * @param {!HeapSnapshotModel.NodeFilter} nodeFilter * @return {undefined|function(!HeapSnapshotWorker.HeapSnapshotNode):boolean} */ _createFilter(nodeFilter) { const minNodeId = nodeFilter.minNodeId; const maxNodeId = nodeFilter.maxNodeId; const allocationNodeId = nodeFilter.allocationNodeId; let filter; if (typeof allocationNodeId === 'number') { filter = this._createAllocationStackFilter(allocationNodeId); filter.key = 'AllocationNodeId: ' + allocationNodeId; } else if (typeof minNodeId === 'number' && typeof maxNodeId === 'number') { filter = this._createNodeIdFilter(minNodeId, maxNodeId); filter.key = 'NodeIdRange: ' + minNodeId + '..' + maxNodeId; } return filter; } /** * @param {!HeapSnapshotModel.SearchConfig} searchConfig * @param {!HeapSnapshotModel.NodeFilter} nodeFilter * @return {!Array.<number>} */ search(searchConfig, nodeFilter) { const query = searchConfig.query; function filterString(matchedStringIndexes, string, index) { if (string.indexOf(query) !== -1) matchedStringIndexes.add(index); return matchedStringIndexes; } const regexp = searchConfig.isRegex ? new RegExp(query) : createPlainTextSearchRegex(query, 'i'); function filterRegexp(matchedStringIndexes, string, index) { if (regexp.test(string)) matchedStringIndexes.add(index); return matchedStringIndexes; } const stringFilter = (searchConfig.isRegex || !searchConfig.caseSensitive) ? filterRegexp : filterString; const stringIndexes = this.strings.reduce(stringFilter, new Set()); if (!stringIndexes.size) return []; const filter = this._createFilter(nodeFilter); const nodeIds = []; const nodesLength = this.nodes.length; const nodes = this.nodes; const nodeNameOffset = this._nodeNameOffset; const nodeIdOffset = this._nodeIdOffset; const nodeFieldCount = this._nodeFieldCount; const node = this.rootNode(); for (let nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) { node.nodeIndex = nodeIndex; if (filter && !filter(node)) continue; if (stringIndexes.has(nodes[nodeIndex + nodeNameOffset])) nodeIds.push(nodes[nodeIndex + nodeIdOffset]); } return nodeIds; } /** * @param {!HeapSnapshotModel.NodeFilter} nodeFilter * @return {!Object.<string, !HeapSnapshotModel.Aggregate>} */ aggregatesWithFilter(nodeFilter) { const filter = this._createFilter(nodeFilter); const key = filter ? filter.key : 'allObjects'; return this.aggregates(false, key, filter); } /** * @param {number} minNodeId * @param {number} maxNodeId * @return {function(!HeapSnapshotWorker.HeapSnapshotNode):boolean} */ _createNodeIdFilter(minNodeId, maxNodeId) { /** * @param {!HeapSnapshotWorker.HeapSnapshotNode} node * @return {boolean} */ function nodeIdFilter(node) { const id = node.id(); return id > minNodeId && id <= maxNodeId; } return nodeIdFilter; } /** * @param {number} bottomUpAllocationNodeId * @return {function(!HeapSnapshotWorker.HeapSnapshotNode):boolean|undefined} */ _createAllocationStackFilter(bottomUpAllocationNodeId) { const traceIds = this._allocationProfile.traceIds(bottomUpAllocationNodeId); if (!traceIds.length) return undefined; const set = {}; for (let i = 0; i < traceIds.length; i++) set[traceIds[i]] = true; /** * @param {!HeapSnapshotWorker.HeapSnapshotNode} node * @return {boolean} */ function traceIdFilter(node) { return !!set[node.traceNodeId()]; } return traceIdFilter; } /** * @param {boolean} sortedIndexes * @param {string=} key * @param {function(!HeapSnapshotWorker.HeapSnapshotNode):boolean=} filter * @return {!Object.<string, !HeapSnapshotModel.Aggregate>} */ aggregates(sortedIndexes, key, filter) { let aggregatesByClassName = key && this._aggregates[key]; if (!aggregatesByClassName) { const aggregates = this._buildAggregates(filter); this._calculateClassesRetainedSize(aggregates.aggregatesByClassIndex, filter); aggregatesByClassName = aggregates.aggregatesByClassName; if (key) this._aggregates[key] = aggregatesByClassName; } if (sortedIndexes && (!key || !this._aggregatesSortedFlags[key])) { this._sortAggregateIndexes(aggregatesByClassName); if (key) this._aggregatesSortedFlags[key] = sortedIndexes; } return aggregatesByClassName; } /** * @return {!Array.<!HeapSnapshotModel.SerializedAllocationNode>} */ allocationTracesTops() { return this._allocationProfile.serializeTraceTops(); } /** * @param {number} nodeId * @return {!HeapSnapshotModel.AllocationNodeCallers} */ allocationNodeCallers(nodeId) { return this._allocationProfile.serializeCallers(nodeId); } /** * @param {number} nodeIndex * @return {?Array.<!HeapSnapshotModel.AllocationStackFrame>} */ allocationStack(nodeIndex) { const node = this.createNode(nodeIndex); const allocationNodeId = node.traceNodeId(); if (!allocationNodeId) return null; return this._allocationProfile.serializeAllocationStack(allocationNodeId); } /** * @return {!Object.<string, !HeapSnapshotModel.AggregateForDiff>} */ aggregatesForDiff() { if (this._aggregatesForDiff) return this._aggregatesForDiff; const aggregatesByClassName = this.aggregates(true, 'allObjects'); this._aggregatesForDiff = {}; const node = this.createNode(); for (const className in aggregatesByClassName) { const aggregate = aggregatesByClassName[className]; const indexes = aggregate.idxs; const ids = new Array(indexes.length); const selfSizes = new Array(indexes.length); for (let i = 0; i < indexes.length; i++) { node.nodeIndex = indexes[i]; ids[i] = node.id(); selfSizes[i] = node.selfSize(); } this._aggregatesForDiff[className] = {indexes: indexes, ids: ids, selfSizes: selfSizes}; } return this._aggregatesForDiff; } /** * @protected * @param {!HeapSnapshotWorker.HeapSnapshotNode} node * @return {boolean} */ isUserRoot(node) { return true; } /** * @param {function(!HeapSnapshotWorker.HeapSnapshotNode)} action * @param {boolean=} userRootsOnly */ forEachRoot(action, userRootsOnly) { for (let iter = this.rootNode().edges(); iter.hasNext(); iter.next()) { const node = iter.edge.node(); if (!userRootsOnly || this.isUserRoot(node)) action(node); } } /** * @param {function(!HeapSnapshotWorker.HeapSnapshotNode,!HeapSnapshotWorker.HeapSnapshotEdge):boolean=} filter */ calculateDistances(filter) { const nodeCount = this.nodeCount; const distances = this._nodeDistances; const noDistance = this._noDistance; for (let i = 0; i < nodeCount; ++i) distances[i] = noDistance; const nodesToVisit = new Uint32Array(this.nodeCount); let nodesToVisitLength = 0; /** * @param {number} distance * @param {!HeapSnapshotWorker.HeapSnapshotNode} node */ function enqueueNode(distance, node) { const ordinal = node.ordinal(); if (distances[ordinal] !== noDistance) return; distances[ordinal] = distance; nodesToVisit[nodesToVisitLength++] = node.nodeIndex; } this.forEachRoot(enqueueNode.bind(null, 1), true); this._bfs(nodesToVisit, nodesToVisitLength, distances, filter); // bfs for the rest of objects nodesToVisitLength = 0; this.forEachRoot(enqueueNode.bind(null, HeapSnapshotModel.baseSystemDistance), false); this._bfs(nodesToVisit, nodesToVisitLength, distances, filter); } /** * @param {!Uint32Array} nodesToVisit * @param {number} nodesToVisitLength * @param {!Int32Array} distances * @param {function(!HeapSnapshotWorker.HeapSnapshotNode,!HeapSnapshotWorker.HeapSnapshotEdge):boolean=} filter */ _bfs(nodesToVisit, nodesToVisitLength, distances, filter) { // Preload fields into local variables for better performance. const edgeFieldsCount = this._edgeFieldsCount; const nodeFieldCount = this._nodeFieldCount; const containmentEdges = this.containmentEdges; const firstEdgeIndexes = this._firstEdgeIndexes; const edgeToNodeOffset = this._edgeToNodeOffset; const edgeTypeOffset = this._edgeTypeOffset; const nodeCount = this.nodeCount; const edgeWeakType = this._edgeWeakType; const noDistance = this._noDistance; let index = 0; const edge = this.createEdge(0); const node = this.createNode(0); while (index < nodesToVisitLength) { const nodeIndex = nodesToVisit[index++]; // shift generates too much garbage. const nodeOrdinal = nodeIndex / nodeFieldCount; const distance = distances[nodeOrdinal] + 1; const firstEdgeIndex = firstEdgeIndexes[nodeOrdinal]; const edgesEnd = firstEdgeIndexes[nodeOrdinal + 1]; node.nodeIndex = nodeIndex; for (let edgeIndex = firstEdgeIndex; edgeIndex < edgesEnd; edgeIndex += edgeFieldsCount) { const edgeType = containmentEdges[edgeIndex + edgeTypeOffset]; if (edgeType === edgeWeakType) continue; const childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; const childNodeOrdinal = childNodeIndex / nodeFieldCount; if (distances[childNodeOrdinal] !== noDistance) continue; edge.edgeIndex = edgeIndex; if (filter && !filter(node, edge)) continue; distances[childNodeOrdinal] = distance; nodesToVisit[nodesToVisitLength++] = childNodeIndex; } } if (nodesToVisitLength > nodeCount) { throw new Error( 'BFS failed. Nodes to visit (' + nodesToVisitLength + ') is more than nodes count (' + nodeCount + ')'); } } /** * @param {function(!HeapSnapshotWorker.HeapSnapshotNode):boolean=} filter * @return {!{aggregatesByClassName: !Object<string, !HeapSnapshotWorker.HeapSnapshot.AggregatedInfo>, * aggregatesByClassIndex: !Object<number, !HeapSnapshotWorker.HeapSnapshot.AggregatedInfo>}} */ _buildAggregates(filter) { const aggregates = {}; const aggregatesByClassName = {}; const classIndexes = []; const nodes = this.nodes; const nodesLength = nodes.length; const nodeNativeType = this._nodeNativeType; const nodeFieldCount = this._nodeFieldCount; const selfSizeOffset = this._nodeSelfSizeOffset; const nodeTypeOffset = this._nodeTypeOffset; const node = this.rootNode(); const nodeDistances = this._nodeDistances; for (let nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) { node.nodeIndex = nodeIndex; if (filter && !filter(node)) continue; const selfSize = nodes[nodeIndex + selfSizeOffset]; if (!selfSize && nodes[nodeIndex + nodeTypeOffset] !== nodeNativeType) continue; const classIndex = node.classIndex(); const nodeOrdinal = nodeIndex / nodeFieldCount; const distance = nodeDistances[nodeOrdinal]; if (!(classIndex in aggregates)) { const nodeType = node.type(); const nameMatters = nodeType === 'object' || nodeType === 'native'; const value = { count: 1, distance: distance, self: selfSize, maxRet: 0, type: nodeType, name: nameMatters ? node.name() : null, idxs: [nodeIndex] }; aggregates[classIndex] = value; classIndexes.push(classIndex); aggregatesByClassName[node.className()] = value; } else { const clss = aggregates[classIndex]; clss.distance = Math.min(clss.distance, distance); ++clss.count; clss.self += selfSize; clss.idxs.push(nodeIndex); } } // Shave off provisionally allocated space. for (let i = 0, l = classIndexes.length; i < l; ++i) { const classIndex = classIndexes[i]; aggregates[classIndex].idxs = aggregates[classIndex].idxs.slice(); } return {aggregatesByClassName: aggregatesByClassName, aggregatesByClassIndex: aggregates}; } /** * @param {!Object<number, !HeapSnapshotWorker.HeapSnapshot.AggregatedInfo>} aggregates * @param {function(!HeapSnapshotWorker.HeapSnapshotNode):boolean=} filter */ _calculateClassesRetainedSize(aggregates, filter) { const rootNodeIndex = this._rootNodeIndex; const node = this.createNode(rootNodeIndex); const list = [rootNodeIndex]; const sizes = [-1]; const classes = []; const seenClassNameIndexes = {}; const nodeFieldCount = this._nodeFieldCount; const nodeTypeOffset = this._nodeTypeOffset; const nodeNativeType = this._nodeNativeType; const dominatedNodes = this._dominatedNodes; const nodes = this.nodes; const firstDominatedNodeIndex = this._firstDominatedNodeIndex; while (list.length) { const nodeIndex = list.pop(); node.nodeIndex = nodeIndex; let classIndex = node.classIndex(); const seen = !!seenClassNameIndexes[classIndex]; const nodeOrdinal = nodeIndex / nodeFieldCount; const dominatedIndexFrom = firstDominatedNodeIndex[nodeOrdinal]; const dominatedIndexTo = firstDominatedNodeIndex[nodeOrdinal + 1]; if (!seen && (!filter || filter(node)) && (node.selfSize() || nodes[nodeIndex + nodeTypeOffset] === nodeNativeType)) { aggregates[classIndex].maxRet += node.retainedSize(); if (dominatedIndexFrom !== dominatedIndexTo) { seenClassNameIndexes[classIndex] = true; sizes.push(list.length); classes.push(classIndex); } } for (let i = dominatedIndexFrom; i < dominatedIndexTo; i++) list.push(dominatedNodes[i]); const l = list.length; while (sizes[sizes.length - 1] === l) { sizes.pop(); classIndex = classes.pop(); seenClassNameIndexes[classIndex] = false; } } } /** * @param {!{aggregatesByClassName: !Object<string, !HeapSnapshotWorker.HeapSnapshot.AggregatedInfo>, aggregatesByClassIndex: !Object<number, !HeapSnapshotWorker.HeapSnapshot.AggregatedInfo>}} aggregates */ _sortAggregateIndexes(aggregates) { const nodeA = this.createNode(); const nodeB = this.createNode(); for (const clss in aggregates) { aggregates[clss].idxs.sort((idxA, idxB) => { nodeA.nodeIndex = idxA; nodeB.nodeIndex = idxB; return nodeA.id() < nodeB.id() ? -1 : 1; }); } } /** * The function checks is the edge should be considered during building * postorder iterator and dominator tree. * * @param {number} nodeIndex * @param {number} edgeType * @return {boolean} */ _isEssentialEdge(nodeIndex, edgeType) { // Shortcuts at the root node have special meaning of marking user global objects. return edgeType !== this._edgeWeakType && (edgeType !== this._edgeShortcutType || nodeIndex === this._rootNodeIndex); } _buildPostOrderIndex() { const nodeFieldCount = this._nodeFieldCount; const nodeCount = this.nodeCount; const rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount; const edgeFieldsCount = this._edgeFieldsCount; const edgeTypeOffset = this._edgeTypeOffset; const edgeToNodeOffset = this._edgeToNodeOffset; const firstEdgeIndexes = this._firstEdgeIndexes; const containmentEdges = this.containmentEdges; const mapAndFlag = this.userObjectsMapAndFlag(); const flags = mapAndFlag ? mapAndFlag.map : null; const flag = mapAndFlag ? mapAndFlag.flag : 0; const stackNodes = new Uint32Array(nodeCount); const stackCurrentEdge = new Uint32Array(nodeCount); const postOrderIndex2NodeOrdinal = new Uint32Array(nodeCount); const nodeOrdinal2PostOrderIndex = new Uint32Array(nodeCount); const visited = new Uint8Array(nodeCount); let postOrderIndex = 0; let stackTop = 0; stackNodes[0] = rootNodeOrdinal; stackCurrentEdge[0] = firstEdgeIndexes[rootNodeOrdinal]; visited[rootNodeOrdinal] = 1; let iteration = 0; while (true) { ++iteration; while (stackTop >= 0) { const nodeOrdinal = stackNodes[stackTop]; const edgeIndex = stackCurrentEdge[stackTop]; const edgesEnd = firstEdgeIndexes[nodeOrdinal + 1]; if (edgeIndex < edgesEnd) { stackCurrentEdge[stackTop] += edgeFieldsCount; const edgeType = containmentEdges[edgeIndex + edgeTypeOffset]; if (!this._isEssentialEdge(nodeOrdinal * nodeFieldCount, edgeType)) continue; const childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; const childNodeOrdinal = childNodeIndex / nodeFieldCount; if (visited[childNodeOrdinal]) continue; const nodeFlag = !flags || (flags[nodeOrdinal] & flag); const childNodeFlag = !flags || (flags[childNodeOrdinal] & flag); // We are skipping the edges from non-page-owned nodes to page-owned nodes. // Otherwise the dominators for the objects that also were retained by debugger would be affected. if (nodeOrdinal !== rootNodeOrdinal && childNodeFlag && !nodeFlag) continue; ++stackTop; stackNodes[stackTop] = childNodeOrdinal; stackCurrentEdge[stackTop] = firstEdgeIndexes[childNodeOrdinal]; visited[childNodeOrdinal] = 1; } else { // Done with all the node children nodeOrdinal2PostOrderIndex[nodeOrdinal] = postOrderIndex; postOrderIndex2NodeOrdinal[postOrderIndex++] = nodeOrdinal; --stackTop; } } if (postOrderIndex === nodeCount || iteration > 1) break; const errors = new HeapSnapshotWorker.HeapSnapshotProblemReport( `Heap snapshot: ${ nodeCount - postOrderIndex } nodes are unreachable from the root. Following nodes have only weak retainers:`); const dumpNode = this.rootNode(); // Remove root from the result (last node in the array) and put it at the bottom of the stack so that it is // visited after all orphan nodes and their subgraphs. --postOrderIndex; stackTop = 0; stackNodes[0] = rootNodeOrdinal; stackCurrentEdge[0] = firstEdgeIndexes[rootNodeOrdinal + 1]; // no need to reiterate its edges for (let i = 0; i < nodeCount; ++i) { if (visited[i] || !this._hasOnlyWeakRetainers(i)) continue; // Add all nodes that have only weak retainers to traverse their subgraphs. stackNodes[++stackTop] = i; stackCurrentEdge[stackTop] = firstEdgeIndexes[i]; visited[i] = 1; dumpNode.nodeIndex = i * nodeFieldCount; const retainers = []; for (let it = dumpNode.retainers(); it.hasNext(); it.next()) retainers.push(`${it.item().node().name()}@${it.item().node().id()}.${it.item().name()}`); errors.addError(`${dumpNode.name()} @${dumpNode.id()} weak retainers: ${retainers.join(', ')}`); } console.warn(errors.toString()); } // If we already processed all orphan nodes that have only weak retainers and still have some orphans... if (postOrderIndex !== nodeCount) { const errors = new HeapSnapshotWorker.HeapSnapshotProblemReport( 'Still found ' + (nodeCount - postOrderIndex) + ' unreachable nodes in heap snapshot:'); const dumpNode = this.rootNode(); // Remove root from the result (last node in the array) and put it at the bottom of the stack so that it is // visited after all orphan nodes and their subgraphs. --postOrderIndex; for (let i = 0; i < nodeCount; ++i) { if (visited[i]) continue; dumpNode.nodeIndex = i * nodeFieldCount; errors.addError(dumpNode.name() + ' @' + dumpNode.id()); // Fix it by giving the node a postorder index anyway. nodeOrdinal2PostOrderIndex[i] = postOrderIndex; postOrderIndex2NodeOrdinal[postOrderIndex++] = i; } nodeOrdinal2PostOrderIndex[rootNodeOrdinal] = postOrderIndex; postOrderIndex2NodeOrdinal[postOrderIndex++] = rootNodeOrdinal; console.warn(errors.toString()); } return { postOrderIndex2NodeOrdinal: postOrderIndex2NodeOrdinal, nodeOrdinal2PostOrderIndex: nodeOrdinal2PostOrderIndex }; } /** * @param {number} nodeOrdinal * @return {boolean} */ _hasOnlyWeakRetainers(nodeOrdinal) { const edgeTypeOffset = this._edgeTypeOffset; const edgeWeakType = this._edgeWeakType; const edgeShortcutType = this._edgeShortcutType; const containmentEdges = this.containmentEdges; const retainingEdges = this._retainingEdges; const beginRetainerIndex = this._firstRetainerIndex[nodeOrdinal]; const endRetainerIndex = this._firstRetainerIndex[nodeOrdinal + 1]; for (let retainerIndex = beginRetainerIndex; retainerIndex < endRetainerIndex; ++retainerIndex) { const retainerEdgeIndex = retainingEdges[retainerIndex]; const retainerEdgeType = containmentEdges[retainerEdgeIndex + edgeTypeOffset]; if (retainerEdgeType !== edgeWeakType && retainerEdgeType !== edgeShortcutType) return false; } return true; } // The algorithm is based on the article: // K. Cooper, T. Harvey and K. Kennedy "A Simple, Fast Dominance Algorithm" // Softw. Pract. Exper. 4 (2001), pp. 1-10. /** * @param {!Array.<number>} postOrderIndex2NodeOrdinal * @param {!Array.<number>} nodeOrdinal2PostOrderIndex */ _buildDominatorTree(postOrderIndex2NodeOrdinal, nodeOrdinal2PostOrderIndex) { const nodeFieldCount = this._nodeFieldCount; const firstRetainerIndex = this._firstRetainerIndex; const retainingNodes = this._retainingNodes; const retainingEdges = this._retainingEdges; const edgeFieldsCount = this._edgeFieldsCount; const edgeTypeOffset = this._edgeTypeOffset; const edgeToNodeOffset = this._edgeToNodeOffset; const firstEdgeIndexes = this._firstEdgeIndexes; const containmentEdges = this.containmentEdges; const rootNodeIndex = this._rootNodeIndex; const mapAndFlag = this.userObjectsMapAndFlag(); const flags = mapAndFlag ? mapAndFlag.map : null; const flag = mapAndFlag ? mapAndFlag.flag : 0; const nodesCount = postOrderIndex2NodeOrdinal.length; const rootPostOrderedIndex = nodesCount - 1; const noEntry = nodesCount; const dominators = new Uint32Array(nodesCount); for (let i = 0; i < rootPostOrderedIndex; ++i) dominators[i] = noEntry; dominators[rootPostOrderedIndex] = rootPostOrderedIndex; // The affected array is used to mark entries which dominators // have to be racalculated because of changes in their retainers. const affected = new Uint8Array(nodesCount); let nodeOrdinal; { // Mark the root direct children as affected. nodeOrdinal = this._rootNodeIndex / nodeFi