Skip to content

Commit f80ff55

Browse files
authored
Fix for endless recursion for getLayoutExplorerNode on a Tooltip (#131486)
![](https://media.giphy.com/media/l0ExdBwqD6YkeEhQ4/giphy-downsized.gif) Fixes flutter/devtools#5946 While preparing DevTools for the Multi View changes, I noticed that inspecting a Tooltip causes an stack overflow. This PR addresses that issue by fixing the scope of the subtreeDepth variable and adding some other idiomatic fixes
1 parent dc4ab0f commit f80ff55

2 files changed

Lines changed: 64 additions & 6 deletions

File tree

‎packages/flutter/lib/src/widgets/widget_inspector.dart‎

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2109,24 +2109,35 @@ mixin WidgetInspectorService {
21092109
summaryTree: true,
21102110
subtreeDepth: subtreeDepth,
21112111
service: this,
2112-
addAdditionalPropertiesCallback: (DiagnosticsNode node, InspectorSerializationDelegate delegate) {
2112+
addAdditionalPropertiesCallback:
2113+
(DiagnosticsNode node, InspectorSerializationDelegate delegate) {
21132114
final Object? value = node.value;
2114-
final RenderObject? renderObject = value is Element ? value.renderObject : null;
2115+
final RenderObject? renderObject =
2116+
value is Element ? value.renderObject : null;
21152117
if (renderObject == null) {
21162118
return const <String, Object>{};
21172119
}
21182120

2119-
final DiagnosticsSerializationDelegate renderObjectSerializationDelegate = delegate.copyWith(
2121+
final DiagnosticsSerializationDelegate
2122+
renderObjectSerializationDelegate = delegate.copyWith(
21202123
subtreeDepth: 0,
21212124
includeProperties: true,
21222125
expandPropertyValues: false,
21232126
);
21242127
final Map<String, Object> additionalJson = <String, Object>{
2125-
'renderObject': renderObject.toDiagnosticsNode().toJsonMap(renderObjectSerializationDelegate),
2128+
// Only include renderObject properties separately if this value is not already the renderObject.
2129+
// Only include if we are expanding property values to mitigate the risk of infinite loops if
2130+
// RenderObjects have properties that are Element objects.
2131+
if (value is! RenderObject && delegate.expandPropertyValues)
2132+
'renderObject': renderObject
2133+
.toDiagnosticsNode()
2134+
.toJsonMap(renderObjectSerializationDelegate),
21262135
};
21272136

21282137
final RenderObject? renderParent = renderObject.parent;
2129-
if (renderParent is RenderObject && subtreeDepth > 0) {
2138+
if (renderParent != null &&
2139+
delegate.subtreeDepth > 0 &&
2140+
delegate.expandPropertyValues) {
21302141
final Object? parentCreator = renderParent.debugCreator;
21312142
if (parentCreator is DebugCreator) {
21322143
additionalJson['parentRenderElement'] =
@@ -2147,7 +2158,7 @@ mixin WidgetInspectorService {
21472158
if (!renderObject.debugNeedsLayout) {
21482159
// ignore: invalid_use_of_protected_member
21492160
final Constraints constraints = renderObject.constraints;
2150-
final Map<String, Object>constraintsProperty = <String, Object>{
2161+
final Map<String, Object> constraintsProperty = <String, Object>{
21512162
'type': constraints.runtimeType.toString(),
21522163
'description': constraints.toString(),
21532164
};

‎packages/flutter/test/widgets/widget_inspector_test.dart‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4771,6 +4771,53 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
47714771
}
47724772
expect(error, isNull);
47734773
});
4774+
4775+
testWidgets(
4776+
'ext.flutter.inspector.getLayoutExplorerNode, on a ToolTip, does not throw StackOverflowError',
4777+
(WidgetTester tester) async {
4778+
// Regression test for https://github.com/flutter/devtools/issues/5946
4779+
const Widget widget = MaterialApp(
4780+
home: Directionality(
4781+
textDirection: TextDirection.ltr,
4782+
child: Center(
4783+
child: Row(
4784+
children: <Widget>[
4785+
Flexible(
4786+
child: ColoredBox(
4787+
color: Colors.green,
4788+
child: Tooltip(
4789+
message: 'a',
4790+
child: ElevatedButton(
4791+
onPressed: null,
4792+
child: Text('a'),
4793+
),
4794+
),
4795+
),
4796+
),
4797+
],
4798+
),
4799+
),
4800+
),
4801+
);
4802+
await tester.pumpWidget(widget);
4803+
4804+
final Element elevatedButton =
4805+
tester.element(find.byType(ElevatedButton).first);
4806+
service.setSelection(elevatedButton, group);
4807+
4808+
final String id = service.toId(elevatedButton, group)!;
4809+
4810+
Object? error;
4811+
try {
4812+
await service.testExtension(
4813+
WidgetInspectorServiceExtensions.getLayoutExplorerNode.name,
4814+
<String, String>{'id': id, 'groupName': group, 'subtreeDepth': '1'},
4815+
);
4816+
} catch (e) {
4817+
error = e;
4818+
}
4819+
expect(error, isNull);
4820+
});
47744821
});
47754822

47764823
test('ext.flutter.inspector.structuredErrors', () async {

0 commit comments

Comments
 (0)