blob: 270dbc709d7d80cff3eb2a88a5497accbac0fee5 [file] [log] [blame]
// // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// // for details. All rights reserved. Use of this source code is governed by a
// // BSD-style license that can be found in the LICENSE file.
//
// import 'dart:typed_data';
//
// import 'package:vm_service/vm_service.dart';
//
// import 'format.dart';
// import 'intset.dart';
// export 'intset.dart';
//
// const int _invalidIdx = 0;
// const int _rootObjectIdx = 1;
//
// class Analysis {
// final HeapSnapshotGraph graph;
//
// late final reachableObjects = transitiveGraph(roots);
//
// late final Uint32List _retainers = _calculateRetainers();
//
// late final _oneByteStringCid = _findClassId('_OneByteString');
// late final _twoByteStringCid = _findClassId('_TwoByteString');
// late final _nonGrowableListCid = _findClassId('_List');
// late final _immutableListCid = _findClassId('_ImmutableList');
// late final _weakPropertyCid = _findClassId('_WeakProperty');
// late final _weakReferenceCid = _findClassId('_WeakReference');
// late final _patchClassCid = _findClassId('PatchClass');
// late final _finalizerEntryCid = _findClassId('FinalizerEntry');
//
// late final _weakPropertyKeyIdx = _findFieldIndex(_weakPropertyCid, 'key_');
// late final _weakPropertyValueIdx =
// _findFieldIndex(_weakPropertyCid, 'value_');
//
// late final _finalizerEntryDetachIdx =
// _findFieldIndex(_finalizerEntryCid, 'detach_');
// late final _finalizerEntryValueIdx =
// _findFieldIndex(_finalizerEntryCid, 'value_');
//
// late final _Arch _arch = (() {
// // We want to figure out the architecture this heapsnapshot was made from
// // without it being directly included in the snapshot.
// // In order to distinguish 32-bit/64-bit/64-bit-compressed we need
// // - an object whose shallowSize will be different for all 3 architectures
// // - have an actual object in the heap snapshot
// // -> PatchClass seems to satisfy this.
// final size = graph.objects
// .firstWhere(
// (obj) => obj.classId == _patchClassCid && obj.shallowSize != 0)
// .shallowSize;
//
// switch (size) {
// case 24:
// return _Arch.arch32;
// case 32:
// return _Arch.arch64c;
// case 48:
// return _Arch.arch64;
// default:
// throw 'Unexpected size of patch class: $size.';
// }
// })();
//
// late final int _headerSize = _arch != _Arch.arch32 ? 8 : 4;
// late final int _wordSize = _arch == _Arch.arch64 ? 8 : 4;
//
// Analysis(this.graph);
//
// /// The roots from which alive data can be discovered.
// final IntSet roots = IntSet()..add(_rootObjectIdx);
//
// /// Calculates retaining paths for all objects in [objs].
// ///
// /// All retaining paths will have the object itself plus at most [depth]
// /// retainers in it.
// List<DedupedUint32List> retainingPathsOf(IntSet objs, int depth) {
// final paths = <DedupedUint32List, int>{};
// for (var oId in objs) {
// final rpath = _retainingPathOf(oId, depth);
// final old = paths[rpath];
// paths[rpath] = (old == null) ? 1 : old + 1;
// }
// paths.forEach((path, count) {
// path.count = count;
// });
// return paths.keys.toList()..sort((a, b) => paths[b]! - paths[a]!);
// }
//
// /// Returns information about a specific object.
// ObjectInformation examine(int oId) {
// String stringifyValue(int valueId) {
// if (valueId == _invalidIdx) return 'int/double/simd';
//
// final object = graph.objects[valueId];
// final cid = object.classId;
// if (cid == _oneByteStringCid || cid == _twoByteStringCid) {
// return '"${truncateString(object.data as String)}"';
// }
//
// final valueClass = graph.classes[cid];
// return '${valueClass.name}@${valueId} (${valueClass.libraryUri})';
// }
//
// final object = graph.objects[oId];
// final cid = object.classId;
// final klass = graph.classes[cid];
// final fs = klass.fields.toList()..sort((a, b) => a.index - b.index);
// final fieldValues = <String, String>{};
// if (cid == _oneByteStringCid || cid == _twoByteStringCid) {
// fieldValues['data'] = stringifyValue(oId);
// } else {
// int maxFieldIndex = -1;
// for (final field in fs) {
// final valueId = object.references[field.index];
// fieldValues[field.name] = stringifyValue(valueId);
// if (field.index > maxFieldIndex) {
// maxFieldIndex = field.index;
// }
// }
//
// if (cid == _immutableListCid || cid == _nonGrowableListCid) {
// final refs = object.references;
// int len = refs.length - (maxFieldIndex + 1);
// if (len < 10) {
// for (int i = 0; i < len; ++i) {
// fieldValues['[$i]'] = stringifyValue(refs[1 + maxFieldIndex + i]);
// }
// } else {
// for (int i = 0; i < 4; ++i) {
// fieldValues['[$i]'] = stringifyValue(refs[1 + maxFieldIndex + i]);
// }
// fieldValues['[...]'] = '';
// for (int i = len - 4; i < len; ++i) {
// fieldValues['[$i]'] = stringifyValue(refs[1 + maxFieldIndex + i]);
// }
// }
// }
// }
// return ObjectInformation(
// klass.name, klass.libraryUri.toString(), fieldValues);
// }
//
// /// Returns information about a specific object.
// ObjectInformation examine2(
// int oId, {
// int maxLevel = 0,
// int level = 0,
// }) {
// Object stringifyValue(int valueId) {
// if (valueId == _invalidIdx) {
// return 'int/double/simd';
// }
//
// if (level < maxLevel) {
// return examine2(valueId, maxLevel: maxLevel, level: level + 1);
// }
//
// final object = graph.objects[valueId];
// final cid = object.classId;
// if (cid == _oneByteStringCid || cid == _twoByteStringCid) {
// return '"${truncateString(object.data as String)}"';
// }
//
// final valueClass = graph.classes[cid];
// return '${valueClass.name}@${valueId} (${valueClass.libraryUri})';
// }
//
// final object = graph.objects[oId];
// final cid = object.classId;
// final klass = graph.classes[cid];
// final fs = klass.fields.toList()..sort((a, b) => a.index - b.index);
// final fieldValues = <String, Object>{};
// if (cid == _oneByteStringCid || cid == _twoByteStringCid) {
// fieldValues['data'] = stringifyValue(oId);
// } else {
// int maxFieldIndex = -1;
// for (final field in fs) {
// final valueId = object.references[field.index];
// fieldValues[field.name] = stringifyValue(valueId);
// if (field.index > maxFieldIndex) {
// maxFieldIndex = field.index;
// }
// }
//
// if (cid == _immutableListCid || cid == _nonGrowableListCid) {
// final refs = object.references;
// int len = refs.length - (maxFieldIndex + 1);
// if (len < 10) {
// for (int i = 0; i < len; ++i) {
// fieldValues['[$i]'] = stringifyValue(refs[1 + maxFieldIndex + i]);
// }
// } else {
// for (int i = 0; i < 4; ++i) {
// fieldValues['[$i]'] = stringifyValue(refs[1 + maxFieldIndex + i]);
// }
// fieldValues['[...]'] = '';
// for (int i = len - 4; i < len; ++i) {
// fieldValues['[$i]'] = stringifyValue(refs[1 + maxFieldIndex + i]);
// }
// }
// }
// }
// return ObjectInformation(
// klass.name, klass.libraryUri.toString(), fieldValues);
// }
//
// /// Generates statistics about the given set of [objects].
// ///
// /// The classes are sored by sum of shallow-size of objects of a class if
// /// [sortBySize] is true and by number of objects per-class otherwise.
// HeapStats generateObjectStats(IntSet objects, {bool sortBySize = true}) {
// final graphObjects = graph.objects;
// final numCids = graph.classes.length;
//
// final counts = Int32List(numCids);
// final sizes = Int32List(numCids);
// for (final objectId in objects) {
// final obj = graphObjects[objectId];
// final cid = obj.classId;
// counts[cid]++;
// sizes[cid] += obj.shallowSize;
// }
//
// final classes = graph.classes.where((c) => counts[c.classId] > 0).toList();
// if (sortBySize) {
// classes.sort((a, b) {
// var diff = sizes[b.classId] - sizes[a.classId];
// if (diff != 0) return diff;
// diff = counts[b.classId] - counts[a.classId];
// if (diff != 0) return diff;
// return graph.classes[b.classId].name
// .compareTo(graph.classes[a.classId].name);
// });
// } else {
// classes.sort((a, b) {
// var diff = counts[b.classId] - counts[a.classId];
// if (diff != 0) return diff;
// diff = sizes[b.classId] - sizes[a.classId];
// if (diff != 0) return diff;
// return graph.classes[b.classId].name
// .compareTo(graph.classes[a.classId].name);
// });
// }
//
// return HeapStats(classes, sizes, counts);
// }
//
// /// Generate statistics about the variable-length data of [objects].
// ///
// /// The returned [HeapData]s are sorted by cumulative size if
// /// [sortBySize] is true and by number of objects otherwise.
// HeapDataStats generateDataStats(IntSet objects, {bool sortBySize = true}) {
// final graphObjects = graph.objects;
// final klasses = graph.classes;
// final counts = <HeapData, int>{};
// for (final objectId in objects) {
// final obj = graphObjects[objectId];
// final klass = klasses[obj.classId].name;
// // Should use length here instead!
// final len = variableLengthOf(obj);
// if (len == -1) continue;
// final data = HeapData(klass, obj.data, obj.shallowSize, len);
// counts[data] = (counts[data] ?? 0) + 1;
// }
// counts.forEach((HeapData data, int count) {
// data.count = count;
// });
//
// final datas = counts.keys.toList();
// if (sortBySize) {
// datas.sort((a, b) => b.totalSize - a.totalSize);
// } else {
// datas.sort((a, b) => b.count - a.count);
// }
//
// return HeapDataStats(datas);
// }
//
// /// Calculates the set of objects transitively reachable by [roots].
// IntSet transitiveGraph(IntSet roots, [TraverseFilter? tfilter = null]) {
// final reachable = IntSet();
// final worklist = <int>[];
//
// final objects = graph.objects;
//
// reachable.addAll(roots);
// worklist.addAll(roots);
//
// final weakProperties = IntSet();
//
// while (worklist.isNotEmpty) {
// while (worklist.isNotEmpty) {
// final objectIdToExpand = worklist.removeLast();
// final objectToExpand = objects[objectIdToExpand];
// final cid = objectToExpand.classId;
//
// // Weak references don't keep their value alive.
// if (cid == _weakReferenceCid) continue;
//
// // Weak properties keep their value alive if the key is alive.
// if (cid == _weakPropertyCid) {
// if (tfilter == null ||
// tfilter._shouldTraverseEdge(
// _weakPropertyCid, _weakPropertyValueIdx)) {
// weakProperties.add(objectIdToExpand);
// }
// continue;
// }
//
// // Normal object (or FinalizerEntry).
// final references = objectToExpand.references;
// final bool isFinalizerEntry = cid == _finalizerEntryCid;
// for (int i = 0; i < references.length; ++i) {
// // [FinalizerEntry] objects don't keep their "detach" and "value"
// // fields alive.
// if (isFinalizerEntry &&
// (i == _finalizerEntryDetachIdx || i == _finalizerEntryValueIdx)) {
// continue;
// }
//
// final successor = references[i];
// if (!reachable.contains(successor)) {
// if (tfilter == null ||
// (tfilter._shouldTraverseEdge(objectToExpand.classId, i) &&
// tfilter._shouldIncludeObject(objects[successor].classId))) {
// reachable.add(successor);
// worklist.add(successor);
// }
// }
// }
// }
//
// // Enqueue values of weak properties if their key is alive.
// weakProperties.removeWhere((int weakProperty) {
// final wpReferences = objects[weakProperty].references;
// final keyId = wpReferences[_weakPropertyKeyIdx];
// final valueId = wpReferences[_weakPropertyValueIdx];
// if (reachable.contains(keyId)) {
// if (!reachable.contains(valueId)) {
// if (tfilter == null ||
// tfilter._shouldIncludeObject(objects[valueId].classId)) {
// reachable.add(valueId);
// worklist.add(valueId);
// }
// }
// return true;
// }
// return false;
// });
// }
// return reachable;
// }
//
// /// Calculates the set of objects that transitively can reach [oids].
// IntSet reverseTransitiveGraph(IntSet oids, [TraverseFilter? tfilter = null]) {
// final reachable = IntSet();
// final worklist = <int>[];
//
// final objects = graph.objects;
//
// reachable.addAll(oids);
// worklist.addAll(oids);
//
// while (worklist.isNotEmpty) {
// final objectIdToExpand = worklist.removeLast();
// final objectToExpand = objects[objectIdToExpand];
// final referrers = objectToExpand.referrers;
// for (int i = 0; i < referrers.length; ++i) {
// final predecessorId = referrers[i];
// // This is a dead object in heap that refers to a live object.
// if (!reachableObjects.contains(predecessorId)) continue;
// if (!reachable.contains(predecessorId)) {
// final predecessor = objects[predecessorId];
// final cid = predecessor.classId;
//
// // A WeakReference does not keep its object alive.
// if (cid == _weakReferenceCid) continue;
//
// // A WeakProperty does not keep its key alive, but may keep it's value
// // alive.
// if (cid == _weakPropertyCid) {
// final refs = predecessor.references;
// bool hasRealRef = false;
// for (int i = 0; i < refs.length; ++i) {
// if (i == _weakPropertyKeyIdx) continue;
// if (refs[i] == objectIdToExpand) hasRealRef = true;
// }
// if (!hasRealRef) continue;
// }
//
// // A FinalizerEntry] does not keep its {detach_,value_} fields alive.
// if (cid == _finalizerEntryCid) {
// final refs = predecessor.references;
// bool hasRealRef = false;
// for (int i = 0; i < refs.length; ++i) {
// if (i == _finalizerEntryDetachIdx) continue;
// if (i == _finalizerEntryValueIdx) continue;
// if (refs[i] == objectIdToExpand) hasRealRef = true;
// }
// if (!hasRealRef) continue;
// }
//
// bool passedFilter = true;
// if (tfilter != null) {
// final index = predecessor.references.indexOf(objectIdToExpand);
// passedFilter =
// (tfilter._shouldTraverseEdge(predecessor.classId, index) &&
// tfilter._shouldIncludeObject(predecessor.classId));
// }
// if (passedFilter) {
// reachable.add(predecessorId);
// worklist.add(predecessorId);
// }
// }
// }
// }
// return reachable;
// }
//
// // Only keep those in [toFilter] that have references from [from].
// IntSet filterObjectsReferencedBy(IntSet toFilter, IntSet from) {
// final result = IntSet();
// final objects = graph.objects;
//
// for (final fromId in from) {
// final from = objects[fromId];
// for (final refId in from.references) {
// if (toFilter.contains(refId)) {
// result.add(refId);
// break;
// }
// }
// }
//
// return result;
// }
//
// /// Returns set of cids that are matching the provided [patterns].
// IntSet findClassIdsMatching(Iterable<String> patterns) {
// final regexPatterns = patterns.map((p) => RegExp(p)).toList();
//
// final classes = graph.classes;
// final cids = IntSet();
// for (final klass in classes) {
// if (regexPatterns.any((pattern) =>
// pattern.hasMatch(klass.name) ||
// pattern.hasMatch(klass.libraryUri.toString()))) {
// cids.add(klass.classId);
// }
// }
// return cids;
// }
//
// /// Create filters that can be used in traversing object graphs.
// TraverseFilter? parseTraverseFilter(List<String> patterns) {
// if (patterns.isEmpty) return null;
//
// final aset = IntSet();
// final naset = IntSet();
//
// int bits = 0;
//
// final fmap = <int, IntSet>{};
// final nfmap = <int, IntSet>{};
// for (String pattern in patterns) {
// final bool isNegated = pattern.startsWith('^');
// if (isNegated) {
// pattern = pattern.substring(1);
// }
//
// // Edge filter.
// final int sep = pattern.indexOf(':');
// if (sep != -1 && sep != (pattern.length - 1)) {
// final klassPattern = pattern.substring(0, sep);
//
// final fieldNamePattern = pattern.substring(sep + 1);
// final cids = findClassIdsMatching([klassPattern]);
//
// final fieldNameRegexp = RegExp(fieldNamePattern);
// for (final cid in cids) {
// final klass = graph.classes[cid];
// for (final field in klass.fields) {
// if (fieldNameRegexp.hasMatch(field.name)) {
// (isNegated ? nfmap : fmap)
// .putIfAbsent(cid, IntSet.new)
// .add(field.index);
// }
// }
// }
//
// if (!isNegated) {
// bits |= TraverseFilter._hasPositiveEdgePatternBit;
// }
//
// continue;
// }
//
// // Class filter.
// final cids = findClassIdsMatching([pattern]);
// (isNegated ? naset : aset).addAll(cids);
//
// if (!isNegated) {
// bits |= TraverseFilter._hasPositiveClassPatternBit;
// }
// }
// return TraverseFilter._(patterns, bits, aset, naset, fmap, nfmap);
// }
//
// /// Returns set of objects from [objectIds] whose class id is in [cids].
// IntSet filterByClassId(IntSet objectIds, IntSet cids) {
// return filter(objectIds, (object) => cids.contains(object.classId));
// }
//
// /// Returns set of objects from [objectIds] whose class id is in [cids].
// IntSet filterByClassPatterns(IntSet objectIds, List<String> patterns) {
// final tfilter = parseTraverseFilter(patterns);
// if (tfilter == null) return objectIds;
// return filter(objectIds, tfilter._shouldFilterObject);
// }
//
// /// Returns set of objects from [objectIds] whose class id is in [cids].
// IntSet filter(IntSet objectIds, bool Function(HeapSnapshotObject) filter) {
// final result = IntSet();
// final objects = graph.objects;
// objectIds.forEach((int objId) {
// if (filter(objects[objId])) {
// result.add(objId);
// }
// });
// return result;
// }
//
// /// Returns users of [objs].
// IntSet findUsers(IntSet objs, List<String> patterns) {
// final tfilter = parseTraverseFilter(patterns);
//
// final objects = graph.objects;
// final result = IntSet();
// for (final objId in objs) {
// final object = objects[objId];
// final referrers = object.referrers;
// for (int i = 0; i < referrers.length; ++i) {
// final userId = referrers[i];
// // This is a dead object in heap that refers to a live object.
// if (!reachableObjects.contains(userId)) continue;
// bool passedFilter = true;
// if (tfilter != null) {
// final user = objects[userId];
// final idx = user.references.indexOf(objId);
// passedFilter = tfilter._shouldTraverseEdge(user.classId, idx) &&
// tfilter._shouldIncludeObject(user.classId);
// }
// if (passedFilter) {
// result.add(userId);
// }
// }
// }
// return result;
// }
//
// /// Returns references of [objs].
// IntSet findReferences(IntSet objs, List<String> patterns) {
// final tfilter = parseTraverseFilter(patterns);
//
// final objects = graph.objects;
// final result = IntSet();
// for (final objId in objs) {
// final object = objects[objId];
// final references = object.references;
// for (int i = 0; i < references.length; ++i) {
// final refId = references[i];
// bool passedFilter = true;
// if (tfilter != null) {
// final other = objects[refId];
// passedFilter = tfilter._shouldTraverseEdge(object.classId, i) &&
// tfilter._shouldIncludeObject(other.classId);
// }
// if (passedFilter) {
// var refObj = graph.objects[refId];
// if (graph.classes[refObj.classId].name == 'FileState') {
// // graph.classes[graph.objects[graph.objects[object.referrers.toList()[0]].referrers[0]].classId]
// // print('aaaaaa');
// }
// result.add(refId);
// }
// }
// }
// return result;
// }
//
// /// Returns the size of the variable part of [object]
// ///
// /// For strings this is the length of the string (or approximation thereof).
// /// For typed data this is the number of elements.
// /// For fixed-length arrays this is the length of the array.
// int variableLengthOf(HeapSnapshotObject object) {
// final cid = object.classId;
//
// final isList = cid == _nonGrowableListCid || cid == _immutableListCid;
// if (isList) {
// // Return the length of the non-growable array.
// final numFields = graph.classes[cid].fields.length;
// return object.references.length - numFields;
// }
//
// final isString = cid == _oneByteStringCid || cid == _twoByteStringCid;
// if (isString) {
// // Return the length of the string.
// //
// // - For lengths <128 the length of string is precise
// // - For larger strings, the data is truncated, so we use the payload
// // size.
// // - TODO: The *heapsnapshot format contains actual length but it gets
// // lost after reading. Can we preserve it somewhere on
// // `HeapSnapshotGraph`?
// //
// // The approximation is based on knowning the header size of a string:
// // - String has: header, length (hash - on 32-bit platforms) + payload
// final fixedSize =
// _headerSize + _wordSize * (_arch == _Arch.arch32 ? 2 : 1);
// final len =
// object.shallowSize == 0 ? 0 : (object.shallowSize - fixedSize);
// if (len < 128) return (object.data as String).length;
// return len; // Over-approximates to 2 * wordsize.
// }
//
// final data = object.data;
// if (data is HeapSnapshotObjectLengthData) {
// // Most likely typed data object, return length in elements.
// return data.length;
// }
//
// final fixedSize = _headerSize + _wordSize * object.references.length;
// final dataSize = object.shallowSize - fixedSize;
// if (dataSize > _wordSize) {
// final klass = graph.classes[cid];
// // User-visible, but VM-recognized objects with variable size.
// if (!['_RegExp', '_SuspendState'].contains(klass.name)) {
// // Non-user-visible, VM-recognized objects (empty library uri).
// final uri = klass.libraryUri.toString().trim();
// if (uri != '') {
// throw 'Object has fixed size: $fixedSize and total '
// 'size: ${object.shallowSize} but is not known to '
// 'be variable-length (class: ${graph.classes[cid].name})';
// }
// }
// }
//
// return -1;
// }
//
// int _findClassId(String className) {
// return graph.classes
// .singleWhere((klass) =>
// klass.name == className &&
// (klass.libraryUri.scheme == 'dart' ||
// klass.libraryUri.toString() == ''))
// .classId;
// }
//
// int _findFieldIndex(int cid, String fieldName) {
// return graph.classes[cid].fields
// .singleWhere((f) => f.name == fieldName)
// .index;
// }
//
// DedupedUint32List _retainingPathOf(int oId, int depth) {
// final objects = graph.objects;
// final classes = graph.classes;
//
// @pragma('vm:prefer-inline')
// int getFieldIndex(int oId, int childId) {
// final object = objects[oId];
// final fields = classes[object.classId].fields;
// final idx = object.references.indexOf(childId);
// if (idx == -1) throw 'should not happen';
//
// int fieldIndex = fields.any((f) => f.index == idx)
// ? idx
// : DedupedUint32List.noFieldIndex;
// return fieldIndex;
// }
//
// @pragma('vm:prefer-inline')
// int retainingPathLength(int id) {
// int length = 1;
// int id = oId;
// while (id != _rootObjectIdx && length <= depth) {
// id = _retainers[id];
// length++;
// }
// return length;
// }
//
// @pragma('vm:prefer-inline')
// bool hasMoreThanOneAlive(IntSet reachableObjects, Uint32List list) {
// int count = 0;
// for (int i = 0; i < list.length; ++i) {
// if (reachableObjects.contains(list[i])) {
// count++;
// if (count >= 2) return true;
// }
// }
// return false;
// }
//
// int lastId = oId;
// var lastObject = objects[lastId];
//
// final path = Uint32List(2 * retainingPathLength(oId) - 1);
// path[0] = lastObject.classId;
// for (int i = 1; i < path.length; i += 2) {
// assert(lastId != _rootObjectIdx && ((i - 1) ~/ 2) < depth);
// final users = lastObject.referrers;
// final int userId = _retainers[lastId];
//
// final user = objects[userId];
// int fieldIndex = getFieldIndex(userId, lastId);
// final lastWasUniqueRef = !hasMoreThanOneAlive(reachableObjects, users);
//
// path[i] = (lastWasUniqueRef ? 1 : 0) << 0 | fieldIndex << 1;
// path[i + 1] = user.classId;
//
// lastId = userId;
// lastObject = user;
// }
// return DedupedUint32List(path);
// }
//
// Uint32List _calculateRetainers() {
// final retainers = Uint32List(graph.objects.length);
//
// var worklist = IntSet()..add(_rootObjectIdx);
// while (!worklist.isEmpty) {
// final next = IntSet();
//
// for (final objId in worklist) {
// final object = graph.objects[objId];
// final cid = object.classId;
//
// // Weak references don't keep their value alive.
// if (cid == _weakReferenceCid) continue;
//
// // Weak properties keep their value alive if the key is alive.
// if (cid == _weakPropertyCid) {
// final valueId = object.references[_weakPropertyValueIdx];
// if (reachableObjects.contains(valueId)) {
// if (retainers[valueId] == 0) {
// retainers[valueId] = objId;
// next.add(valueId);
// }
// }
// continue;
// }
//
// // Normal object (or FinalizerEntry).
// final references = object.references;
// final bool isFinalizerEntry = cid == _finalizerEntryCid;
// for (int i = 0; i < references.length; ++i) {
// // [FinalizerEntry] objects don't keep their "detach" and "value"
// // fields alive.
// if (isFinalizerEntry &&
// (i == _finalizerEntryDetachIdx || i == _finalizerEntryValueIdx)) {
// continue;
// }
//
// final refId = references[i];
// if (retainers[refId] == 0) {
// retainers[refId] = objId;
// next.add(refId);
// }
// }
// }
// worklist = next;
// }
// return retainers;
// }
// }
//
// class TraverseFilter {
// static const int _hasPositiveClassPatternBit = (1 << 0);
// static const int _hasPositiveEdgePatternBit = (1 << 1);
//
// final List<String> _patterns;
//
// final int _bits;
//
// final IntSet? _allowed;
// final IntSet? _disallowed;
//
// final Map<int, IntSet>? _followMap;
// final Map<int, IntSet>? _notFollowMap;
//
// const TraverseFilter._(this._patterns, this._bits, this._allowed,
// this._disallowed, this._followMap, this._notFollowMap);
//
// bool get _hasPositiveClassPattern =>
// (_bits & _hasPositiveClassPatternBit) != 0;
// bool get _hasPositiveEdgePattern => (_bits & _hasPositiveEdgePatternBit) != 0;
//
// String asString(HeapSnapshotGraph graph) {
// final sb = StringBuffer();
// sb.writeln(
// 'The traverse filter expression "${_patterns.join(' ')}" matches:\n');
//
// final ca = _allowed ?? IntSet();
// final cna = _disallowed ?? IntSet();
//
// final klasses = graph.classes.toList()
// ..sort((a, b) => a.name.compareTo(b.name));
//
// for (final klass in klasses) {
// final cid = klass.classId;
//
// final posEdge = [];
// final negEdge = [];
//
// final f = _followMap?[cid] ?? IntSet();
// final nf = _notFollowMap?[cid] ?? IntSet();
// for (final field in klass.fields) {
// final fieldIndex = field.index;
// if (f.contains(fieldIndex)) {
// posEdge.add(field.name);
// }
// if (nf.contains(fieldIndex)) {
// negEdge.add(field.name);
// }
// }
//
// bool printedClass = false;
// final name = klass.name;
// if (ca.contains(cid)) {
// sb.writeln('[+] $name');
// printedClass = true;
// }
// if (cna.contains(cid)) {
// sb.writeln('[-] $name');
// printedClass = true;
// }
// if (posEdge.isNotEmpty || negEdge.isNotEmpty) {
// if (!printedClass) {
// sb.writeln('[ ] $name');
// printedClass = true;
// }
// for (final field in posEdge) {
// sb.writeln('[+] .$field');
// }
// for (final field in negEdge) {
// sb.writeln('[-] .$field');
// }
// }
// }
// return sb.toString().trim();
// }
//
// // Should include the edge when building transitive graphs.
// bool _shouldTraverseEdge(int cid, int fieldIndex) {
// final nf = _notFollowMap?[cid];
// if (nf != null && nf.contains(fieldIndex)) return false;
//
// final f = _followMap?[cid];
// if (f != null && f.contains(fieldIndex)) return true;
//
// // If there's an allow list we only allow allowed ones, otherwise we allow
// // all.
// return !_hasPositiveEdgePattern;
// }
//
// // Should include the object when building transitive graphs.
// bool _shouldIncludeObject(int cid) {
// if (_disallowed?.contains(cid) == true) return false;
// if (_allowed?.contains(cid) == true) return true;
//
// // If there's an allow list we only allow allowed ones, otherwise we allow
// // all.
// return !_hasPositiveClassPattern;
// }
//
// // Should include the object when filtering a set of objects.
// bool _shouldFilterObject(HeapSnapshotObject object) {
// final cid = object.classId;
// final numReferences = object.references.length;
// return __shouldFilterObject(cid, numReferences);
// }
//
// bool __shouldFilterObject(int cid, int numReferences) {
// if (!_shouldIncludeObject(cid)) return false;
//
// // Check if the object has an explicitly disallowed field.
// final nf = _notFollowMap?[cid];
// if (nf != null) {
// for (int fieldIndex = 0; fieldIndex < numReferences; ++fieldIndex) {
// if (nf.contains(fieldIndex)) return false;
// }
// }
//
// // Check if the object has an explicitly allowed field.
// final f = _followMap?[cid];
// if (f != null) {
// for (int fieldIndex = 0; fieldIndex < numReferences; ++fieldIndex) {
// if (f.contains(fieldIndex)) return true;
// }
// }
//
// // If there's an allow list we only allow allowed ones, otherwise we allow
// // all.
// return !_hasPositiveEdgePattern;
// }
// }
//
// /// Stringified representation of a heap object.
// class ObjectInformation {
// final String className;
// final String libraryUri;
// final Map<String, Object> fieldValues;
//
// ObjectInformation(this.className, this.libraryUri, this.fieldValues);
// }
//
// /// Heap usage statistics calculated for a set of heap objects.
// class HeapStats {
// final List<HeapSnapshotClass> classes;
// final Int32List sizes;
// final Int32List counts;
//
// HeapStats(this.classes, this.sizes, this.counts);
//
// int get totalSize => sizes.fold(0, (int a, int b) => a + b);
// int get totalCount => counts.fold(0, (int a, int b) => a + b);
// }
//
// /// Heap object data statistics calculated for a set of heap objects.
// class HeapDataStats {
// final List<HeapData> datas;
//
// HeapDataStats(this.datas);
//
// int get totalSizeUniqueDatas =>
// datas.fold(0, (int sum, HeapData d) => sum + d.size);
// int get totalSize =>
// datas.fold(0, (int sum, HeapData d) => sum + d.totalSize);
// int get totalCount => datas.fold(0, (int sum, HeapData d) => sum + d.count);
// }
//
// /// Representing the data of one heap object.
// ///
// /// Since the data can be truncated, it has an extra size that allows to
// /// distinguish datas with same truncated value with high probability.
// class HeapData {
// final String klass;
// final dynamic value;
// final int size;
// final int len;
//
// late final int count;
//
// HeapData(this.klass, this.value, this.size, this.len);
//
// int? _hashCode;
// int get hashCode {
// if (_hashCode != null) return _hashCode!;
//
// var valueToHash = value;
// if (valueToHash is! String &&
// valueToHash is! bool &&
// valueToHash is! double) {
// if (valueToHash is HeapSnapshotObjectLengthData) {
// valueToHash = valueToHash.length;
// } else if (valueToHash is HeapSnapshotObjectNoData) {
// valueToHash = 0;
// } else if (valueToHash is HeapSnapshotObjectNullData) {
// valueToHash = 0;
// } else {
// throw '${valueToHash.runtimeType}';
// }
// }
//
// return _hashCode = Object.hash(klass, valueToHash, size, len);
// }
//
// bool operator ==(other) {
// if (identical(this, other)) return true;
// if (other is! HeapData) return false;
// if (size != other.size) return false;
// if (len != other.len) return false;
// if (klass != other.klass) return false;
//
// final ovalue = other.value;
// if (value is String || value is bool || value is double) {
// return value == ovalue;
// }
// // We don't have the typed data content, so we don't know whether they are
// // equal / dedupable.
// return false;
// }
//
// String get valueAsString {
// var d = value;
// if (d is String) {
// final newLine = d.indexOf('\n');
// if (newLine >= 0) {
// d = d.substring(0, newLine);
// }
// if (d.length > 80) {
// d = d.substring(0, 80);
// }
// return d;
// }
// return 'len:$len';
// }
//
// int get totalSize => size * count;
// }
//
// /// Used to represent retaining paths.
// ///
// /// For retaining paths: `[cid0, fieldIdx1 << 1 | isUniqueOwner, cid1, ...]`
// class DedupedUint32List {
// static const int noFieldIndex = (1 << 29);
//
// final Uint32List path;
// late final int count;
//
// DedupedUint32List(this.path);
//
// int? _hashCode;
// int get hashCode => _hashCode ??= Object.hashAll(path);
//
// bool operator ==(other) {
// if (identical(this, other)) return true;
// if (other is! DedupedUint32List) return false;
// if (path.length != other.path.length) return false;
// for (int i = 0; i < path.length; ++i) {
// if (path[i] != other.path[i]) return false;
// }
// return true;
// }
// }
//
// enum _Arch {
// arch32,
// arch64,
// arch64c,
// }