Skip to content

Fix issue with stack traces getting mangled#59900

Merged
dnfield merged 4 commits into
flutter:masterfrom
dnfield:weird_stack_bug
Jun 23, 2020
Merged

Fix issue with stack traces getting mangled#59900
dnfield merged 4 commits into
flutter:masterfrom
dnfield:weird_stack_bug

Conversation

@dnfield

@dnfield dnfield commented Jun 19, 2020

Copy link
Copy Markdown
Contributor

Description

Certain types of async errors can get through with a mangled stack trace today, which causes a failure noted in the linked bug - the stack frame parser does not want stack traces as they come from package:stack_trace, we want the raw ones.

Related Issues

Fixes #59893

Tests

I added the following tests:

Test that throws that kind of error and then that we don't run into issues trying to parse it. Also adds an assertion that we don't get an unmangled trace.

/cc @PixelToast

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.

@dnfield
dnfield requested review from Hixie, goderbauer and pingbird June 19, 2020 23:53
@fluttergithubbot fluttergithubbot added a: tests "flutter test", flutter_test, or one of our tests framework flutter/packages/flutter repository. See also f: labels. labels Jun 19, 2020
@fluttergithubbot

This comment has been minimized.

@dnfield

dnfield commented Jun 19, 2020

Copy link
Copy Markdown
Contributor Author

(original commit was missing the newly added test file)

@pingbird

Copy link
Copy Markdown
Member

Instead of dealing with the mangled stack traces in onError, I think it would be more beneficial to avoid non-vm stack traces being propagated in the first place because this may still crop up in other places.

In my minimal repo, the problematic trace comes from the ZoneSpecification errorCallback defined here: https://github.com/dart-lang/stack_trace/blob/4d1ef4eddcd9b18c23bf19fd9d37245d3c81fa2e/lib/src/stack_zone_specification.dart#L172

What we could do is define an errorCallback in our own flutter_test zone which demangles it, and then just replace the FlutterErrorDetails copy in onError with an assertion.

Alternatively, we could modify package:stack_trace to make sure implementers of StackTrace only produce traces consistent with the current platform, requiring anyone that depended on mangled traces to call a separate method.

@dnfield

dnfield commented Jun 20, 2020

Copy link
Copy Markdown
Contributor Author

I think the probem with setting up another zone for this is that the test, as written, is directly calling FlutterError.reportError with a mangled stack trace. It's not going through the zone error handling mechanism - it's created and caught in the test itself. This is a constructed case, but it matches what I've seen in some nested network exceptions elsewhere.

It's not clear to me where or why the package:stack_trace types are getting introduced. Perhaps from package:test?

I also wouldn't bank on getting package:stack_trace changed here - part of the point of that package is to make traces look more human friendly, as defined by that package.

Comment thread packages/flutter/lib/src/foundation/assertions.dart Outdated
Comment thread packages/flutter_test/lib/src/binding.dart Outdated
Comment thread packages/flutter_test/test/bindings_async_gap_test.dart
Comment thread packages/flutter_test/test/bindings_async_gap_test.dart
@pingbird

pingbird commented Jun 21, 2020

Copy link
Copy Markdown
Member

This is my first review, so please tell me if I'm doing anything wrong 😰

Yes, package:test is the one applying the zone to flutter tests: https://github.com/dart-lang/test/blob/658e47e99c170605f7ab7502c556aae295ed9f46/pkgs/test_api/lib/src/backend/invoker.dart#L386

I think the approach of demangling in onError will work to address any current bugs. The reason for my concerns about them being propagated to error handlers is that a few different places in flutter still make the assumption that a stack trace isn't mangled (and we could make it again in the future).

After some digging it looks like the only uses of StackFrame parsing outside of FlutterError.onError are in some tests that call toDiagnosticsNode or dumpErrorToConsole directly:

final FlutterErrorDetails details = FlutterErrorDetails(
exception: 'Example exception',
stack: StackTrace.current,
library: 'Example library',
context: ErrorDescription('Example context'),
informationCollector: () sync* {
yield ErrorDescription('Example information');
},
);
FlutterError.dumpErrorToConsole(details);

These will throw if a mangled stack is ever passed to them in the future.

All three of us (!) originally thought this was coming from the vm, so at the very least StackFrame.fromStackTraceLine should throw a better error message when given a mangled stack.

@pingbird

pingbird commented Jun 21, 2020

Copy link
Copy Markdown
Member

I found a bunch of other places this could fail, basically any handler of FlutterError.onError that calls toDiagnosticsNode on the error details, directly or indirectly.

TestRenderingFlutterBinding:

TestRenderingFlutterBinding({ this.onErrors }) {
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.dumpErrorToConsole(details);
Zone.current.parent.handleUncaughtError(details.exception, details.stack);
};
}

FlutterError.onError = (FlutterErrorDetails details) {

Tap tests:

FlutterError.onError = (FlutterErrorDetails details) {
expect(details.toString().contains('"spontaneous onTapCancel"') , isTrue);
gotError = true;
};

A scaffold test:

FlutterError.onError = (FlutterErrorDetails error) => errors.add(error);

Row tests: https://github.com/flutter/flutter/blob/4d7525f05c05a6df0b29396bc9eb78c3bf1e9f89/packages/flutter/test/widgets/row_test.dart

An object test:

FlutterError.onError = (FlutterErrorDetails details) {

@dnfield

dnfield commented Jun 22, 2020

Copy link
Copy Markdown
Contributor Author

A lot of those other areas are more specialized and not exposed for use in end-users tests. I think we should fix them because it could be confusing for framework developers, but probably do so in another patch - unless you think there's a generalized solution for them.

One thing I don't think we can do is have this check exist in the stack filter, since we don't want to add a dependency on package:stack_trace to the framework.

@pingbird

Copy link
Copy Markdown
Member

Yeah, I couldn't come up with a good way for these to be filtered in the framework either (w/o the added dependency), maybe hix or goderbaur knows more.

/// The [exception] must not be null; other arguments can be left to
/// their default values. (`throw null` results in a
/// [NullThrownError] exception.)
// If you add a property to this class, do not forget to update [copyWith].

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should we say this on every class with a copyWith?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's fair, I had this in here before adding copy with. Removing.

@Hixie

Hixie commented Jun 23, 2020

Copy link
Copy Markdown
Contributor

We now call _unmangle in three places; does this make any of the preexisting ones redundant?

@goderbauer goderbauer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM modulo Hixie's comment about redundancy.

@dnfield

dnfield commented Jun 23, 2020

Copy link
Copy Markdown
Contributor Author

AFAICT, we could reduce the calls to _unmangle in handleUncaughtError, but that's a different path than FlutterError.reportError, at least potentially.

Basically, we have to deal with the case where an uncaught exception (sync or async) is thrown in the test, and we have to deal with the case where FlutterError.reportError is called directly. Either one can happen in a test, and they go through two different paths here.

@dnfield

dnfield commented Jun 23, 2020

Copy link
Copy Markdown
Contributor Author

To explain - we could reassing the stack variable using _unmangle instead of calling _unmangle on it in two places. But we'd still have two distinct places in real code (plus one in the assert I added).

@Hixie

Hixie commented Jun 23, 2020

Copy link
Copy Markdown
Contributor

I've no problem it being in three places so long as we're confident that none of those places are entirely redundant (i.e. are entirely subsumed by one of the others). Test frameworks are inherently pretty crazy because they have to deal with unpredictable failures almost by definition.

@dnfield
dnfield merged commit 868c4d8 into flutter:master Jun 23, 2020
@dnfield
dnfield deleted the weird_stack_bug branch June 23, 2020 22:30
@flutter-github-sync

Copy link
Copy Markdown

Google testing passed!

mingwandroid pushed a commit to mingwandroid/flutter that referenced this pull request Sep 6, 2020
@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Jul 30, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

a: tests "flutter test", flutter_test, or one of our tests framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stack frame parser is crashing with malformed asynchronous gap lines

7 participants