in packages/devtools_app/lib/src/debugger/debugger_model.dart [339:499]
Future<void> buildVariablesTree(
DartObjectNode variable, {
bool expandAll = false,
}) async {
final ref = variable.ref;
if (!variable.isExpandable || variable.treeInitializeStarted || ref == null)
return;
variable.treeInitializeStarted = true;
final isolateRef = ref.isolateRef;
final instanceRef = ref.instanceRef;
final diagnostic = ref.diagnostic;
if (diagnostic != null && includeDiagnosticPropertiesInDebugger) {
final service = diagnostic.inspectorService;
Future<void> _addPropertiesHelper(
List<RemoteDiagnosticsNode> properties) async {
if (properties == null) return;
await addExpandableChildren(
variable,
await _createVariablesForDiagnostics(
service,
properties,
isolateRef,
),
expandAll: true,
);
}
if (diagnostic.inlineProperties?.isNotEmpty ?? false) {
await _addPropertiesHelper(diagnostic.inlineProperties);
} else {
assert(!service.disposed);
if (!service.disposed) {
await _addPropertiesHelper(await diagnostic.getProperties(service));
}
}
}
final existingNames = <String>{};
for (var child in variable.children) {
final name = child?.name;
if (name != null && name.isNotEmpty) {
existingNames.add(name);
if (!isPrivate(name)) {
// Assume private and public names with the same name reference the same
// data so showing both is not useful.
existingNames.add('_$name');
}
}
}
if (variable.childCount > DartObjectNode.MAX_CHILDREN_IN_GROUPING) {
final numChildrenInGrouping =
variable.childCount >= pow(DartObjectNode.MAX_CHILDREN_IN_GROUPING, 2)
? (roundToNearestPow10(variable.childCount) /
DartObjectNode.MAX_CHILDREN_IN_GROUPING)
.floor()
: DartObjectNode.MAX_CHILDREN_IN_GROUPING;
var start = variable.offset ?? 0;
final end = start + variable.childCount;
while (start < end) {
final count = min(end - start, numChildrenInGrouping);
variable.addChild(
DartObjectNode.grouping(variable.ref, offset: start, count: count),
);
start += count;
}
} else if (instanceRef != null && serviceManager.service != null) {
try {
final dynamic result =
await _getObjectWithRetry(instanceRef.id, variable);
if (result is Instance) {
if (result.associations != null) {
variable.addAllChildren(
_createVariablesForAssociations(result, isolateRef));
} else if (result.elements != null) {
variable
.addAllChildren(_createVariablesForElements(result, isolateRef));
} else if (result.bytes != null) {
variable.addAllChildren(_createVariablesForBytes(result, isolateRef));
// Check fields last, as all instanceRefs may have a non-null fields
// with no entries.
} else if (result.fields != null) {
variable.addAllChildren(_createVariablesForFields(result, isolateRef,
existingNames: existingNames));
}
}
} on SentinelException {
// Fail gracefully if calling `getObject` throws a SentinelException.
}
}
if (diagnostic != null && includeDiagnosticChildren) {
// Always add children last after properties to avoid confusion.
final ObjectGroupBase service = diagnostic.inspectorService;
final diagnosticChildren = await diagnostic.children;
if (diagnosticChildren?.isNotEmpty ?? false) {
final childrenNode = DartObjectNode.text(
pluralize('child', diagnosticChildren.length, plural: 'children'),
);
variable.addChild(childrenNode);
await addExpandableChildren(
childrenNode,
await _createVariablesForDiagnostics(
service,
diagnosticChildren,
isolateRef,
),
expandAll: expandAll,
);
}
}
final inspectorService = serviceManager.inspectorService;
if (inspectorService != null) {
final tasks = <Future>[];
ObjectGroupBase group;
Future<void> _maybeUpdateRef(DartObjectNode child) async {
if (child.ref == null) return;
if (child.ref.diagnostic == null) {
// TODO(jacobr): also check whether the InstanceRef is an instance of
// Diagnosticable and show the Diagnosticable properties in that case.
final instanceRef = child.ref.instanceRef;
// This is an approximation of eval('instanceRef is DiagnosticsNode')
// TODO(jacobr): cache the full class hierarchy so we can cheaply check
// instanceRef is DiagnosticsNode without having to do an eval.
if (instanceRef != null &&
(instanceRef.classRef?.name == 'DiagnosticableTreeNode' ||
instanceRef.classRef?.name == 'DiagnosticsProperty')) {
// The user is expecting to see the object the DiagnosticsNode is
// describing not the DiagnosticsNode itself.
try {
group ??= inspectorService.createObjectGroup('temp');
final valueInstanceRef = await group.evalOnRef(
'object.value',
child.ref,
);
// TODO(jacobr): add the Diagnostics properties as well?
child._ref = GenericInstanceRef(
isolateRef: isolateRef,
value: valueInstanceRef,
);
} catch (e) {
if (e is! SentinelException) {
log('Caught $e accessing the value of an object',
LogLevel.warning);
}
}
}
}
}
for (var child in variable.children) {
tasks.add(_maybeUpdateRef(child));
}
if (tasks.isNotEmpty) {
await Future.wait(tasks);
unawaited(group?.dispose());
}
}
variable.treeInitializeComplete = true;
}