-
Notifications
You must be signed in to change notification settings - Fork 26.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Break on "unhandled" exceptions when a debugger is attached #17007
Comments
@DanTup who has the next action on this issue, do you think? I've just been struggling with exactly this. |
I think this has been discussed a little (w/ @devoncarew @a-siva) but it sounds like there's no easy/obvious solution. The error isn't "unhandled" anymore because Flutter catches it to render the red screen/error. All the solutions I can come up with are kinda weird (like Flutter just not catching unhandled exceptions, or there being some way for Flutter to signal that this catching of an error should still break the debugger prior to handling it). It does still bug me a lot too (I've been making lots of crashes lately doing maths in my tiler library 🙈). When an error occurs, I'd much prefer the debugger to break and let me examine variables than give me incomplete information in the Debug Console and then I have to repro it with a breakpoint. |
@DanTup I'd love to see this issue fixed, too. I run into it myself. Can you post a small repro that we can use to drive a solution? |
I'm not sure how complete a repro you're after, but any code that generates some exception that you might want to examine state for works. For example I did When this exception occurs and I'm running in debug mode, what I would like is for the debugger to pause on that line (as it would in a Dart CLI application or What actually happens if we just get info written to the Debug Console (as if we ran without debugging): This tells me that the variable was null, but doesn't help me understand why it is null. When this happens, I currently have to either add a breakpoint to that line or enable break-on-all-exceptions, but both have drawbacks:
In addition, I need to be able to reproduce the issue again - and if I don't know what triggered it the first time I hit it, this might take some time. I understand this might not be the best way to debug some Flutter issues, but it feels like it's been "taken away" when using Flutter (it works for Dart CLI and |
cc @mraleph as we discussed this today. From Slava, one solution might be to mark the Flutter framework exception handling method with a special pragma (I-handle-framework-errors). When we encounter an exception, the VM looks up the stack to see if the exception is handled or not. The framework exception hander means that all exceptions from build methods are technically handled, so we'll never break on uncaught exceptions. The difference w/ this new pragma marked method would be that, for the purposes of the break-on-uncaught-exceptions, the VM would notice the pragma method on the stack, and could then decide to break at the place where the exception was thrown. The user would then be able to inspect local variables and state at the place where the exception occured. also cc'ing @mkustermann, who's been working on improvements to async stack handling (and may have context in this area) |
cc'ing @goderbauer - I'm assuming that there is a good framework method that we'd want to mark w/ this pragma |
Could you hook into That method is called from various places in the framework after we've caught an exception. Should we also break when the user has set up some custom error-handling, e.g. by providing their own |
I believe it would need to be something that was on the stack when the exception was thrown - something that might itself call FlutterError.reportError(). From a very quick perusal of the framework code, that might be something like |
Actually, it looks like that method is not on the stack either; something like |
If you just mark |
@goderbauer I think the only robust solution here is to introduce a special method @pragma('vm:exception-swallower')
void swallow<T>(T Function() code) {
try {
return code();
} catch (e, st) {
// Report e & st and do nothing.
}
} and then rewrite all other code to use this pattern. Other patterns are very hard to support - note that we need to know at the moment when we throw exception that the catch which is going to catch it is a "swallowing" catch. Because you want to stop execution at the moment of throw - not after exception was caught. |
For synchronous exceptions propagated up the stack, it would be not very difficult to make the debugger break on unhandled exceptions (as well as exceptions that would be caught by a function that was marked with a pragma annotation). Making this also work for async functions is probably quite a bit more challenging and might only work in a subset of situations. |
@cskau-g Could you look into the "synchronous" part of this issue? Some more explanation: |
TEST=runtime/tests/vm/dart{,_2}/notify_debugger_on_exception_test.dart Bug: flutter/flutter#17007 Change-Id: I988d2385c3d0fc42c4eb769312278261720bb68d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/176663 Commit-Queue: Clement Skau <cskau@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
This reverts commit 9b45fcb. Reason for revert: Broke kernel-precomp Original change's description: > [VM] Adds @pragma('vm:notify-debugger-on-exception') > > TEST=runtime/tests/vm/dart{,_2}/notify_debugger_on_exception_test.dart > > Bug: flutter/flutter#17007 > Change-Id: I988d2385c3d0fc42c4eb769312278261720bb68d > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/176663 > Commit-Queue: Clement Skau <cskau@google.com> > Reviewed-by: Martin Kustermann <kustermann@google.com> TBR=kustermann@google.com,cskau@google.com Change-Id: Ib0ec0ad0a0e2e11f8088c3f57c4085ffc840d1f3 No-Presubmit: true No-Tree-Checks: true No-Try: true Bug: flutter/flutter#17007 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/176664 Reviewed-by: Clement Skau <cskau@google.com> Commit-Queue: Clement Skau <cskau@google.com>
Note: This is a reland: - Relocates the tests. - Adds status file skip for dartkp. TEST=runtime/observatory{,_2}/tests/service{,_2}/notify_debugger_on_exception_test.dart Bug: flutter/flutter#17007 Change-Id: I5c79c140c5e7dee9dfeaa6d4bf750cf4d72f6e85 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/177580 Commit-Queue: Clement Skau <cskau@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
A CL has landed, adding I believe this addresses everything on the Dart side, so I'll resolve the issue here. |
Will this still require some changes in Flutter (adding that |
I have added the pragma to the VM, but I have not otherwise changed anything about the way exceptions are handled in Dart or Flutter. So for the new mechanism to take effect you'll need some function in the path of the exception to be annotated with the pragma. If you're just interested in temporarily pausing the debugger on all exceptions as if they were uncaught, you can add the pragma in places like But it might also be that this could be used through-out Flutter for more general effect - but I'm definitely not the right person to make such a call. |
I think we should probably reopen because somebody needs to go through Flutter framework and annotate places where exceptions are swallowed, e.g. example from #17007 (comment) needs annotation on |
Unassigning myself since I'm probably not the right person for the Flutter changes. |
@cskau-g It appears that the pragma doesn't work on async methods: void main() {
foo();
}
@pragma('vm:notify-debugger-on-exception')
Future<void> foo() async { // <<<< note the async here
try {
throw 'Foo';
} catch (e) {
print('exception caught');
}
return Future.value();
} Is that a known limitation or a bug that could be addressed? There's one occurrence in framework where we'd need to apply it to an
|
Yeah, I think I know why it does not work - we missed this in the implementation. If this is high priority let me know and I will take a look myself, otherwise we wait until @cskau-g is back, he is currently away for a bit. |
It's not urgent. I filed a bug over on the dart sdk to keep track of this specific issue: dart-lang/sdk#45673 |
@mraleph Is there a way to apply the pragma to anonymous closures? void main() {
bar('Hello World');
}
@pragma('vm:notify-debugger-on-exception')
void bar(String someObject) {
foo(() {
try {
print(someObject);
throw 'ERROR';
} catch (e) { // <-- this catch should notify the debugger
print('exception caught');
}
});
}
void foo(Function function) {
function();
} Short of refactoring the code to have the try/catch not be part of the closure, I haven't found a way. For the particular instance in the framework, that kind of refactor would make the code a lot more complicated, I believe. This is the code in the framework that has this problem: flutter/packages/flutter/lib/src/widgets/layout_builder.dart Lines 117 to 152 in 989a2f2
|
I ran into another issue with the debugger not breaking on the line that causes the exception, but on a (random?) different line instead. This one appears to be unrelated to the pragma and reproduces even without it. I've filed dart-lang/sdk#45684 with steps to reproduce. |
After a good night's sleep I thought the problem described in #17007 (comment) can be resolved by refactoring the code as follows: void main() {
bar('Hello World');
}
void bar(String someObject) {
@pragma('vm:notify-debugger-on-exception')
void myFunction() {
try {
print(someObject);
throw 'ERROR'; // <-- attached debugger expected to break here
} catch (e) {
print('exception caught');
}
}
foo(myFunction);
}
void foo(Function function) {
function();
} Unfortunately, the debugger still doesn't break at all. I've filed dart-lang/sdk#45710 for this issue. |
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of |
Currently when there are exceptions in Flutter, they get swallowed by the framework and rendered on the device. This might be better than crashing when not debugging but if a debugger is attached it seems wrong to pass up breaking and letting the user inspect the exception and state. Otherwise the user needs to read the stack, but a breakpoint in the appropriate place and then reproduce the issue (which is what break-on-unhandled-exceptions should avoid us having to do).
There was a little discussion in #2357 about this, and there was some suggestion that users could use "break on all exceptions", however I really don't think that's a good solution. Users often have exceptions being caught, or use third party code that is sloppy with it's exceptions. For all intents and purposes, these exceptions are uncaught.
The text was updated successfully, but these errors were encountered: