Skip to content

Conversation

@dnfield
Copy link
Contributor

@dnfield dnfield commented Jan 8, 2020

Description

Elides repetitive tree walking related frames from stack traces in Flutter.

For example, a stack trace resulting from a bad deeply nested widget now prints out 29 lines instead of over 300.

I'm not sure if we want to have a debug flag for disabling this - I personally have never cared about how many tree walks happened when debugging framework issues though. /cc @chunhtai @Piinks @goderbauer for feedback on that.

@Hixie @a14n @zanderso @jonahwilliams probably have thoughts on this too.

Related Issues

Fixes #9329

Tests

I added the following tests:

Test asserting defaultStackFilter filters a huge ugly stack down.

Checklist

Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes ([x]). This will ensure a smooth and quick review process.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I signed the CLA.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Did any tests fail when you ran them? Please read Handling breaking changes.

  • No, no existing tests failed, so this is not a breaking change.

@fluttergithubbot fluttergithubbot added the framework flutter/packages/flutter repository. See also f: labels. label Jan 8, 2020
} else if (frameLine.packageScheme == 'package' && frameLine.package == 'flutter') {
if (visited.add('flutter/${frameLine.packagePath}:${frameLine.line}:${frameLine.column}')) {
if (skippedFromFlutterCount > 0) {
result.add('(elided $skippedFromFlutterCount frame${skippedFromFlutterCount == 1 ? '' : 's'} from package:flutter)');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe: 'elided 2 revisited frames ...' or something similar so that there's some explanation of what was left out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked offline. We should figure out what the best thing for a user is here. It might just be chopping the stack N frames of flutter code after user code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would IDE users ever want to expand out the elided frames? If so, it could be worth it to expose the elided frames with enough structure that IDEs can expose expanding out the elided frames.

The ideal solution would be that an IDE received this data with enough structure to expand and collapse blocks of elided frames if requested by an IDE user who wants to see the elided frames.

I do like placing the logic of what frames should start out elided in package:flutter instead of having intellij, devtools, and VSCode create their own logic about what frames to elide when displaying flutter error messages.
It would be even better if consistent frame black boxing was also applied to stack traces displayed by IDEs but that would be a hard problem to solve.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally don't think it's ever useful to see all of these frames. Or, if it is, it's so rare that it's worth just instrumenting the code differently in the framework by hand.

Do the IDEs use the defaultStackFilter, or do they replace it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDEs currently use defaultStackFilter. If eliding frames gets aggressive enough that users sometimes need to see the frames, we could revisit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If people need to debug something tricky there's always the option of adding breakpoints/break-on-all-exceptions where they'd get the full stack in the debugging tools.

In VS Code we do collapse multiple frames from SDKs that one (eg. "<105 frames from Flutter SDK>") but you can click-to-expand.

I suspect few people would miss these frames being removed from here (in VS Code at least) if they're still there in the debugger call stack.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debuggers are more or less useless when debugging this code, because stepping through what we do even for a single frame takes hours. Stack inspection and printf-debugging is really the only reasonable way I've found to debug errors deep in the framework.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, fair enough! I thought break-on-all-exceptions would break you in a good place but I've really no experience trying to debug these sorts of issues so I'll defer to the experts :-)

Jacob's original idea sounds good though - if all stack frames are provided to the IDE in a structured way with flags on which ones are reasonable to elide, then for IDEs that have an easy way to collapse them (but still make them accessible - similar to VS Code's call stack), that might be best of both worlds (for locations where interaction isn't possible, the whole list could be shown, or have a user preference - similar to how we let users device whether to have Framework libraries debuggable or not).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally the stack traces I'm trying to divine from are on GitHub, no IDEs in sight. :-)

@dnfield
Copy link
Contributor Author

dnfield commented Jan 8, 2020

@InMatrix - what do you think of this?

@DanTup @jacob314 @devoncarew - will this negatively impact IDEs?

@dnfield
Copy link
Contributor Author

dnfield commented Jan 8, 2020

Stack traces from the framework generally have this shape:

  1. Top few frames are Dart code for throwing the exception. We already elide these.
  2. The next few frames are either user code or Flutter code that called into Dart to throw.
  3. If the previous frames were flutter code, these frames are user code.
  4. Lots of frames of flutter code with the shape of *Element.mount followed by several lines of build related logic followed by another *Element.mount, repeated down to the root element.
  5. Attaching to the tree frames
  6. Timer related frames from Dart
  7. ReceivePortImpl frame from Dart.

We could probably safely omit all Flutter frames after the first mount call we find.

@dnfield
Copy link
Contributor Author

dnfield commented Jan 8, 2020

Eliding everything after the first mount would look something like:

I/flutter ( 4364): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 4364): The following assertion was thrown building Builder(dirty):
I/flutter ( 4364): A non-null String must be provided to a Text widget.
I/flutter ( 4364): 'package:flutter/src/widgets/text.dart':
I/flutter ( 4364): Failed assertion: line 287 pos 10: 'data != null'
I/flutter ( 4364): 
I/flutter ( 4364): The relevant error-causing widget was:
I/flutter ( 4364):   Builder file:///Users/dnfield/src/flutter_projects/assertions/lib/main.dart:5:10
I/flutter ( 4364): 
I/flutter ( 4364): When the exception was thrown, this was the stack:
I/flutter ( 4364): #2      new Text (package:flutter/src/widgets/text.dart:287:10)
I/flutter ( 4364): #3      blah.<anonymous closure> (package:assertions/main.dart:7:20)
I/flutter ( 4364): #4      Builder.build (package:flutter/src/widgets/basic.dart:6825:41)
I/flutter ( 4364): #5      StatelessElement.build (package:flutter/src/widgets/framework.dart:4371:28)
I/flutter ( 4364): #6      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4303:15)
I/flutter ( 4364): #7      Element.rebuild (package:flutter/src/widgets/framework.dart:4027:5)
I/flutter ( 4364): #8      ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4286:5)
I/flutter ( 4364): #9      ComponentElement.mount (package:flutter/src/widgets/framework.dart:4281:5)
I/flutter ( 4364): #452    _Timer._runTimers (dart:isolate-patch/timer_impl.dart:384:19)
I/flutter ( 4364): #453    _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:418:5)
I/flutter ( 4364): #454    _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174:12)
I/flutter ( 4364): (elided 444 frames from class _AssertionError, package dart:async, package dart:async-patch, and package flutter)
I/flutter ( 4364): 
I/flutter ( 4364): ════════════════════════════════════════════════════════════════════════════════════════════════════

@zanderso
Copy link
Member

zanderso commented Jan 8, 2020

Supposing that mount is the right cut-off point, this approach sgtm.

@Piinks Piinks added the a: error message Error messages from the Flutter framework label Jan 8, 2020
@dnfield dnfield added the a: debugging Debugging, breakpoints, expression evaluation label Jan 8, 2020
@dnfield
Copy link
Contributor Author

dnfield commented Jan 8, 2020

This breaks some tests that are asserting against error messgaes - I'll update the tests if there's consensus that this is a good approach.

@Hixie
Copy link
Contributor

Hixie commented Jan 8, 2020

It's a bit more subtle than the discussion above suggests.

It's not only Flutter code in these iterations. For example, users can create their own Elements or RenderObjects and then their code will be present in these loops. It's critical that users be able to see this information when debugging their own logic.

The iterations are not perfectly repeated. For example, some widgets have custom elements, and they might have their own mounting logic. When you're doing layout, every node has its own performLayout method. Thus every one out of every 3 or 4 frames is actually subtly different in a potentially useful way. I've definitely often made use of this information.

What we could do is recognize specific patterns (building, mounting, unmounting widgets; layout or paint of render objects; semantics; etc) and replace the relevant parts of the stack with a clean description of what's happening, e.g. "laying out render objects: RenderStack (stack.dart:302), RenderDecoratedBox (proxy_box.dart:201), RenderFlex (flex.dart:585), RenderFlex (flex.dart:585)" or something.

'#348 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:384:19)',
'#349 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:418:5)',
'#350 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174:12)',
'(elided 330 frames from package flutter)'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here in particular i think we are eliding way too much valuable information. It'd be better if between #9 and #348 we said something like "Inflated 28 elements". Even more than that though, because consider frames #21 or #60 for example, notice how those are different from the rest of the pattern.

I definitely wouldn't elide frames #333 to #339 inclusive. I think on the other hand we should elide #348 to #350, that we don't is just an oversight in the current code.

BTW we should make these tests verify the output on real stacks generated by the test, because we want to catch future regressions where Dart or the Flutter framework change structure and the logic here bitrots again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can properly handle user code this way, but if it's a custom widget we wouldn't omit it here because that would live outside of package:flutter.

Part of the problem is a stack trace line just doesn't give us enough information (particularly on web, where we don't even get the class name). What might be possible is iterating from the bottom up until we hit a mount call from framework.dart, then group those together until we get to the one before we hit user code (and repeat if there's a custom element in user code).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine if we do a bad job on Web because Web doesn't give us enough yet. We should aim for doing the best we can on each platform we support, and not get tied down by the least common denominator.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could have pattern matchers for specific stack patterns we know about. if we do that, then there should be a way for these patterns to get registered by the bindings, so that each layer can register its own matchers.

@dnfield
Copy link
Contributor Author

dnfield commented Jan 10, 2020

@Hixie I think the stack you're thinking of is different from what I'm used to seeing. Probably a throw during layout?

In any case, I tried just cleaning up the mount loops like this:

#2      new Text (package:flutter/src/widgets/text.dart:287:10)
#3      _MyHomePageState.build (package:assertions/main.dart:100:15)
#4      StatefulElement.build (package:flutter/src/widgets/framework.dart:4414:27)
#5      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4303:15)
#6      Element.rebuild (package:flutter/src/widgets/framework.dart:4027:5)
#7      ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4286:5)
#8      StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4461:11)
#9      ComponentElement.mount (package:flutter/src/widgets/framework.dart:4281:5)
#10+2   SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#13+5   ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#19+2   SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#22+2   SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#25+6   ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#32+2   SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#35+2   SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#38+6   ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#45+5   ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#51+6   ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#58+2   SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#61+5   ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#67+2   SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#70+6   ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#77+5   ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#83+2   SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#86+5   ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#92+6   ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#99+6   ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#106+1  MultiChildRenderObjectElement: mount -> inflateWidget
#108+9  ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget -> mount -> updateChild -> inflateWidget
#118+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#124+2  SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#127+6  ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#134+2  SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#137+2  SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#140+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#146+6  ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#153+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#159+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#165+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#171+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#177+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#183+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#189+6  ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#196+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#202+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#208+2  SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#211+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#217+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#223+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#229+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#235+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#241+2  SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#244+6  ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#251+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#257+6  ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#264+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#270+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#276+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#282+2  SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#285+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#291+6  ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#298+6  ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#305+6  ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#312+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#318+6  ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#325+5  ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#331    Element.inflateWidget (package:flutter/src/widgets/framework.dart:3276:14)
#332    Element.updateChild (package:flutter/src/widgets/framework.dart:3070:12)
#333    RenderObjectToWidgetElement._rebuild (package:flutter/src/widgets/binding.dart:1054:16)
#334    RenderObjectToWidgetElement.mount (package:flutter/src/widgets/binding.dart:1025:5)
#335    RenderObjectToWidgetAdapter.attachToRenderTree.<anonymous closure> (package:flutter/src/widgets/binding.dart:968:17)
#336    BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2494:19)
#337    RenderObjectToWidgetAdapter.attachToRenderTree (package:flutter/src/widgets/binding.dart:967:13)
#338    WidgetsBinding.attachRootWidget (package:flutter/src/widgets/binding.dart:848:7)
#339    WidgetsBinding.scheduleAttachRootWidget.<anonymous closure> (package:flutter/src/widgets/binding.dart:830:7)
(elided 3 frames from dart:isolate-patch)

But it's still looking too verbose to me.

@dnfield
Copy link
Contributor Author

dnfield commented Jan 10, 2020

The significant difference being that all of these calls are coming from the same file and the same two or three generic framework elements - so nothing nice about "oh, this one was for a stack, this one was for a row" etc.

@InMatrix
Copy link

For the specific error example @dnfield showed earlier in this PR, nothing in the stack is useful or necessary for the user to debug the error. If the lines above the stack are not enough for the user to understand the error, the user is more likely to google the error message than digging through the stack.

I/flutter ( 4364): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 4364): The following assertion was thrown building Builder(dirty):
I/flutter ( 4364): A non-null String must be provided to a Text widget.
I/flutter ( 4364): 'package:flutter/src/widgets/text.dart':
I/flutter ( 4364): Failed assertion: line 287 pos 10: 'data != null'
I/flutter ( 4364): 
I/flutter ( 4364): The relevant error-causing widget was:
I/flutter ( 4364):   Builder file:///Users/dnfield/src/flutter_projects/assertions/lib/main.dart:5:10
I/flutter ( 4364): 
I/flutter ( 4364): When the exception was thrown, this was the stack:
I/flutter ( 4364): #2      new Text (package:flutter/src/widgets/text.dart:287:10)
I/flutter ( 4364): #3      blah.<anonymous closure> (package:assertions/main.dart:7:20)
I/flutter ( 4364): #4      Builder.build (package:flutter/src/widgets/basic.dart:6825:41)
I/flutter ( 4364): #5      StatelessElement.build (package:flutter/src/widgets/framework.dart:4371:28)
I/flutter ( 4364): #6      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4303:15)
I/flutter ( 4364): #7      Element.rebuild (package:flutter/src/widgets/framework.dart:4027:5)
I/flutter ( 4364): #8      ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4286:5)
I/flutter ( 4364): #9      ComponentElement.mount (package:flutter/src/widgets/framework.dart:4281:5)
I/flutter ( 4364): #452    _Timer._runTimers (dart:isolate-patch/timer_impl.dart:384:19)
I/flutter ( 4364): #453    _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:418:5)
I/flutter ( 4364): #454    _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174:12)
I/flutter ( 4364): (elided 444 frames from class _AssertionError, package dart:async, package dart:async-patch, and package flutter)
I/flutter ( 4364): 
I/flutter ( 4364): ════════════════════════════════════════════════════════════════════════════════════════════════════

It would be helpful to use an example where the stack actually has information the user will need to debug the error.

@dnfield
Copy link
Contributor Author

dnfield commented Jan 10, 2020

Perhaps rather than dumping the full Flutter stack, we should dump the truncated stack (just the first few frames in Flutter land), and then some kind of helpful summary of the current state of render/element/layer/semantics tree stuff?

@Hixie
Copy link
Contributor

Hixie commented Jan 10, 2020

For the specific error example @dnfield showed earlier in this PR, nothing in the stack is useful or necessary for the user to debug the error.

Sure, but how can we distinguish those from cases where they are necessary?

But it's still looking too verbose to me.

Can we collapse identical lines in that stack too?

@Hixie
Copy link
Contributor

Hixie commented Jan 10, 2020

e.g.:

#2      new Text (package:flutter/src/widgets/text.dart:287:10)
#3      _MyHomePageState.build (package:assertions/main.dart:100:15)
#4      StatefulElement.build (package:flutter/src/widgets/framework.dart:4414:27)
#5      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4303:15)
#6      Element.rebuild (package:flutter/src/widgets/framework.dart:4027:5)
#7      ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4286:5)
#8      StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4461:11)
#9      ComponentElement.mount (package:flutter/src/widgets/framework.dart:4281:5)
#10     SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#13     ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#19     SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget (x2)
#25     ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#32     SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget (x2)
#38     ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#45     ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#51     ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#58     SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#61     ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#67     SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#70     ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#77     ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#83     SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#86     ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#92     ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget (x2)
#106    MultiChildRenderObjectElement: mount -> inflateWidget
#108    ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget -> mount -> updateChild -> inflateWidget
#118    ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#124    SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#127    ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#134    SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget (x2)
#140    ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#146    ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#153    ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget (x6)
#189    ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#196    ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget (x2)
#208    SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#211    ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget (x5)
#241    SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#244    ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#251    ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#257    ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#264    ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget (x3)
#282    SingleChildRenderObjectElement: mount -> updateChild -> inflateWidget
#285    ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#291    ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget (x3)
#312    ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#318    ComponentElement: mount -> _firstBuild -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#325    ComponentElement: mount -> _firstBuild -> rebuild -> performRebuild -> updateChild -> inflateWidget
#331    Element.inflateWidget (package:flutter/src/widgets/framework.dart:3276:14)
#332    Element.updateChild (package:flutter/src/widgets/framework.dart:3070:12)
#333    RenderObjectToWidgetElement._rebuild (package:flutter/src/widgets/binding.dart:1054:16)
#334    RenderObjectToWidgetElement.mount (package:flutter/src/widgets/binding.dart:1025:5)
#335    RenderObjectToWidgetAdapter.attachToRenderTree.<anonymous closure> (package:flutter/src/widgets/binding.dart:968:17)
#336    BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2494:19)
#337    RenderObjectToWidgetAdapter.attachToRenderTree (package:flutter/src/widgets/binding.dart:967:13)
#338    WidgetsBinding.attachRootWidget (package:flutter/src/widgets/binding.dart:848:7)
#339    WidgetsBinding.scheduleAttachRootWidget.<anonymous closure> (package:flutter/src/widgets/binding.dart:830:7)

Though I just noticed that where this says ComponentElement it should really say either StatefulElement or StatelessElement, to be more useful...

@Hixie
Copy link
Contributor

Hixie commented Jan 10, 2020

If you recognized those lines as matching the "mounting elements" patterns (see my earlier suggestion for defined pattern matchers), you could go further:

e.g.:

#2      new Text (package:flutter/src/widgets/text.dart:287:10)
#3      _MyHomePageState.build (package:assertions/main.dart:100:15)
#4      Normal mounting of elements (StatefulElement, StatelessElement, SingleChildRenderObjectElement)
#331    Element.inflateWidget (package:flutter/src/widgets/framework.dart:3276:14)
#332    Element.updateChild (package:flutter/src/widgets/framework.dart:3070:12)
#333    RenderObjectToWidgetElement._rebuild (package:flutter/src/widgets/binding.dart:1054:16)
#334    RenderObjectToWidgetElement.mount (package:flutter/src/widgets/binding.dart:1025:5)
#335    RenderObjectToWidgetAdapter.attachToRenderTree.<anonymous closure> (package:flutter/src/widgets/binding.dart:968:17)
#336    BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2494:19)
#337    RenderObjectToWidgetAdapter.attachToRenderTree (package:flutter/src/widgets/binding.dart:967:13)
#338    WidgetsBinding.attachRootWidget (package:flutter/src/widgets/binding.dart:848:7)
#339    WidgetsBinding.scheduleAttachRootWidget.<anonymous closure> (package:flutter/src/widgets/binding.dart:830:7)

@Hixie
Copy link
Contributor

Hixie commented Jan 10, 2020

Or maybe

#4      Normal mounting of 35 elements

(or whatever the count is, I didn't actually count it.)

If there was a special element involved, then it would still show its stack because it wouldn't match the pattern.

@Hixie
Copy link
Contributor

Hixie commented Jan 10, 2020

@Hixie I think the stack you're thinking of is different from what I'm used to seeing. Probably a throw during layout?

Layout has similar patterns and similar needs, sure. Paint, also. The widget framework has a number of different cases where there's tree iteration that has patterns we could recognize, like the one you've listed here but also things like exceptions during parent data application, global key activation, dependency changes, etc.

@InMatrix
Copy link

Sure, but how can we distinguish those from cases where they are necessary?

I'm genuinely curious about the cases when information in the stack is necessary. Without anyone in the thread presenting such as case, it makes whoever hasn't experienced such cases themselves very difficult to participate in this discussion and be convinced that the final resolution is reasonable (i.e., striking a good balance between cost and benefit).

@dnfield
Copy link
Contributor Author

dnfield commented Jan 10, 2020

I like @Hixie's idea of having various bindings register stack recognition routines. I think that could get us something more useful here without completely truncating the stack.

@jacob314
Copy link
Contributor

jacob314 commented Jan 10, 2020

Fyi @kenzieschmoll, @bkonyi.
The stack recognition routines suggested would also be extremely useful for the Dart DevTools timeline flame charts. The flame charts for Flutter apps currently have a lot of visual clutter due to the same repetitive stack trace patterns.

To integrate into the flame chart display it would probably be simpler to expose registering the stack recognition routines in dart:developer rather than package:flutter. This would also have the lower priority benefit of enabling future usage any time VSCode or IntelliJ show stack traces.

@Hixie
Copy link
Contributor

Hixie commented Jan 10, 2020

@InMatrix For example at home I'm experimenting with a library which introduces a bunch of new RenderObject subclasses and corresponding widgets, as well as a different set of subclasses of RenderObjectElement. When I messed up my logic in my RenderObjectElement subclass, I had to figure out exactly what was going wrong by studying the stacks I was getting.

Similarly we were recently debugging an issue with GlobalKey reparenting, where the precise order of activate/deactivate/etc matters to figuring out what's wrong.

const PartialStackFrame componentElementFristBuild = PartialStackFrame(className: 'ComponentElement', method: '_firstBuild', packagePath: 'src/widgets/framework.dart');
const PartialStackFrame componentElementMount = PartialStackFrame(className: 'ComponentElement', method: 'mount', packagePath: 'src/widgets/framework.dart');
const PartialStackFrame statefulElementFristBuild = PartialStackFrame(className: 'StatefulElement', method: '_firstBuild', packagePath: 'src/widgets/framework.dart');
const PartialStackFrame singleChildMount = PartialStackFrame(className: 'SingleChildRenderObjectElement', method: 'mount', packagePath: 'src/widgets/framework.dart');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it would be nice if these filters could include package:flutter in the package path to eliminate the small chance of false positives.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


/// The method name for this frame line.
///
/// On web, private methods are wrapped with `[]`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe reference dart-lang/sdk#40117 here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor

@jacob314 jacob314 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@dnfield
Copy link
Contributor Author

dnfield commented Jan 24, 2020

I'm going to land this on green - @Hixie if any concerns remain we can continue to iterate on it.

@fluttergithubbot
Copy link
Contributor

This pull request is not suitable for automatic merging in its current state.

  • Please get at least one approved review before re-applying this label. Reviewers: If you left a comment approving, please use the "approve" review action instead.

@goderbauer
Copy link
Member

Looks like Cirrus is unhappy on this one now.

@dnfield
Copy link
Contributor Author

dnfield commented Jan 29, 2020

Should be fixed on latest commit. I forgot to update the regex string in a test.

@fluttergithubbot
Copy link
Contributor

This pull request is not suitable for automatic merging in its current state.

  • Please get at least one approved review before re-applying this label. Reviewers: If you left a comment approving, please use the "approve" review action instead.

@dnfield dnfield merged commit 46ccd08 into flutter:master Jan 31, 2020
@dnfield dnfield deleted the stack_assert branch January 31, 2020 15:09
shihaohong pushed a commit that referenced this pull request Jan 31, 2020
shihaohong pushed a commit that referenced this pull request Jan 31, 2020
dnfield added a commit to dnfield/flutter that referenced this pull request Jan 31, 2020
This was referenced Jan 31, 2020
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 1, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

a: debugging Debugging, breakpoints, expression evaluation a: error message Error messages from the Flutter framework framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stack frame filtering should collapse long tree walks

10 participants