mirror of
https://github.com/dart-lang/sdk
synced 2024-10-05 17:35:02 +00:00
Improve Observatory debugger behaviour when an isolate exits with unhandled exceptions
- When an isolate is paused at exit and has a sticky error, have the debugger display the error. - Add --pause-isolates-on-unhandled-exceptions flag which sets a default value for break-on-exception. - Have the debugger display help text about 'set break-on-exception' and '--pause-isolates-on-unhandled-exceptions' - Signal an unhandled exception even if no client is connected to Observatory. R=rmacnak@google.com Review URL: https://codereview.chromium.org/1636083002 .
This commit is contained in:
parent
82de52aae3
commit
ec020a301b
|
@ -1378,7 +1378,6 @@ class ObservatoryDebugger extends Debugger {
|
|||
if ((breakOnException != iso.exceptionsPauseInfo) &&
|
||||
(iso.exceptionsPauseInfo != null)) {
|
||||
breakOnException = iso.exceptionsPauseInfo;
|
||||
console.print("Now pausing for exceptions: $breakOnException");
|
||||
}
|
||||
|
||||
_isolate.reload().then((response) {
|
||||
|
@ -1493,6 +1492,25 @@ class ObservatoryDebugger extends Debugger {
|
|||
warnOutOfDate();
|
||||
}
|
||||
|
||||
void _reportIsolateError(Isolate isolate) {
|
||||
if (isolate == null) {
|
||||
return;
|
||||
}
|
||||
DartError error = isolate.error;
|
||||
if (error == null) {
|
||||
return;
|
||||
}
|
||||
console.newline();
|
||||
console.printBold('Isolate exited due to an unhandled exception:');
|
||||
console.print(error.message);
|
||||
console.newline();
|
||||
console.printBold("Type 'set break-on-exception Unhandled' to pause the"
|
||||
" isolate when an unhandled exception occurs.");
|
||||
console.newline();
|
||||
console.printBold("You can make this the default by running with "
|
||||
"--pause-isolates-on-unhandled-exceptions");
|
||||
}
|
||||
|
||||
void _reportPause(ServiceEvent event) {
|
||||
if (event.kind == ServiceEvent.kPauseStart) {
|
||||
console.print(
|
||||
|
@ -1502,6 +1520,7 @@ class ObservatoryDebugger extends Debugger {
|
|||
console.print(
|
||||
"Paused at isolate exit "
|
||||
"(type 'continue' or [F7] to exit the isolate')");
|
||||
_reportIsolateError(isolate);
|
||||
} else if (stack['frames'].length > 0) {
|
||||
Frame frame = stack['frames'][0];
|
||||
var script = frame.location.script;
|
||||
|
@ -1618,8 +1637,11 @@ class ObservatoryDebugger extends Debugger {
|
|||
case ServiceEvent.kPauseInterrupted:
|
||||
case ServiceEvent.kPauseException:
|
||||
if (event.owner == isolate) {
|
||||
_refreshStack(event).then((_) {
|
||||
_refreshStack(event).then((_) async {
|
||||
flushStdio();
|
||||
if (isolate != null) {
|
||||
await isolate.reload();
|
||||
}
|
||||
_reportPause(event);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
// VMOptions=--error_on_bad_type --error_on_bad_override
|
||||
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'test_helper.dart';
|
||||
import 'dart:async';
|
||||
|
||||
doThrow() {
|
||||
throw "TheException"; // Line 13.
|
||||
return "end of doThrow";
|
||||
}
|
||||
|
||||
var tests = [
|
||||
hasStoppedWithUnhandledException,
|
||||
(Isolate isolate) async {
|
||||
var stack = await isolate.getStack();
|
||||
expect(stack['frames'][0].function.name, equals('doThrow'));
|
||||
}
|
||||
];
|
||||
|
||||
main(args) => runIsolateTests(args,
|
||||
tests,
|
||||
pause_on_unhandled_exceptions: true,
|
||||
testeeConcurrent: doThrow);
|
|
@ -26,7 +26,10 @@ class _TestLauncher {
|
|||
Platform.script.toFilePath(),
|
||||
_TESTEE_MODE_FLAG] {}
|
||||
|
||||
Future<int> launch(bool pause_on_start, bool pause_on_exit, bool trace_service) {
|
||||
Future<int> launch(bool pause_on_start,
|
||||
bool pause_on_exit,
|
||||
bool pause_on_unhandled_exceptions,
|
||||
bool trace_service) {
|
||||
assert(pause_on_start != null);
|
||||
assert(pause_on_exit != null);
|
||||
assert(trace_service != null);
|
||||
|
@ -41,6 +44,9 @@ class _TestLauncher {
|
|||
if (pause_on_exit) {
|
||||
fullArgs.add('--pause-isolates-on-exit');
|
||||
}
|
||||
if (pause_on_unhandled_exceptions) {
|
||||
fullArgs.add('--pause-isolates-on-unhandled-exceptions');
|
||||
}
|
||||
fullArgs.addAll(Platform.executableArguments);
|
||||
fullArgs.addAll(args);
|
||||
print('** Launching $dartExecutable ${fullArgs.join(' ')}');
|
||||
|
@ -110,7 +116,8 @@ void runIsolateTests(List<String> mainArgs,
|
|||
bool pause_on_start: false,
|
||||
bool pause_on_exit: false,
|
||||
bool trace_service: false,
|
||||
bool verbose_vm: false}) {
|
||||
bool verbose_vm: false,
|
||||
bool pause_on_unhandled_exceptions: false}) {
|
||||
assert(!pause_on_start || testeeBefore == null);
|
||||
if (mainArgs.contains(_TESTEE_MODE_FLAG)) {
|
||||
if (!pause_on_start) {
|
||||
|
@ -128,7 +135,8 @@ void runIsolateTests(List<String> mainArgs,
|
|||
}
|
||||
} else {
|
||||
var process = new _TestLauncher();
|
||||
process.launch(pause_on_start, pause_on_exit, trace_service).then((port) {
|
||||
process.launch(pause_on_start, pause_on_exit,
|
||||
pause_on_unhandled_exceptions, trace_service).then((port) {
|
||||
if (mainArgs.contains("--gdb")) {
|
||||
port = 8181;
|
||||
}
|
||||
|
@ -157,15 +165,14 @@ void runIsolateTests(List<String> mainArgs,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
Future<Isolate> hasStoppedAtBreakpoint(Isolate isolate) {
|
||||
Future<Isolate> hasPausedFor(Isolate isolate, String kind) {
|
||||
// Set up a listener to wait for breakpoint events.
|
||||
Completer completer = new Completer();
|
||||
isolate.vm.getEventStream(VM.kDebugStream).then((stream) {
|
||||
var subscription;
|
||||
subscription = stream.listen((ServiceEvent event) {
|
||||
if (event.kind == ServiceEvent.kPauseBreakpoint) {
|
||||
print('Breakpoint reached');
|
||||
if (event.kind == kind) {
|
||||
print('Paused with $kind');
|
||||
subscription.cancel();
|
||||
if (completer != null) {
|
||||
// Reload to update isolate.pauseEvent.
|
||||
|
@ -178,9 +185,9 @@ Future<Isolate> hasStoppedAtBreakpoint(Isolate isolate) {
|
|||
// Pause may have happened before we subscribed.
|
||||
isolate.reload().then((_) {
|
||||
if ((isolate.pauseEvent != null) &&
|
||||
(isolate.pauseEvent.kind == ServiceEvent.kPauseBreakpoint)) {
|
||||
(isolate.pauseEvent.kind == kind)) {
|
||||
// Already waiting at a breakpoint.
|
||||
print('Breakpoint reached');
|
||||
print('Paused with $kind');
|
||||
subscription.cancel();
|
||||
if (completer != null) {
|
||||
completer.complete(isolate);
|
||||
|
@ -193,6 +200,13 @@ Future<Isolate> hasStoppedAtBreakpoint(Isolate isolate) {
|
|||
return completer.future; // Will complete when breakpoint hit.
|
||||
}
|
||||
|
||||
Future<Isolate> hasStoppedAtBreakpoint(Isolate isolate) {
|
||||
return hasPausedFor(isolate, ServiceEvent.kPauseBreakpoint);
|
||||
}
|
||||
|
||||
Future<Isolate> hasStoppedWithUnhandledException(Isolate isolate) {
|
||||
return hasPausedFor(isolate, ServiceEvent.kPauseException);
|
||||
}
|
||||
|
||||
Future<Isolate> hasPausedAtStart(Isolate isolate) {
|
||||
// Set up a listener to wait for breakpoint events.
|
||||
|
@ -343,7 +357,8 @@ Future runVMTests(List<String> mainArgs,
|
|||
bool pause_on_start: false,
|
||||
bool pause_on_exit: false,
|
||||
bool trace_service: false,
|
||||
bool verbose_vm: false}) async {
|
||||
bool verbose_vm: false,
|
||||
bool pause_on_unhandled_exceptions: false}) async {
|
||||
if (mainArgs.contains(_TESTEE_MODE_FLAG)) {
|
||||
if (!pause_on_start) {
|
||||
if (testeeBefore != null) {
|
||||
|
@ -362,6 +377,7 @@ Future runVMTests(List<String> mainArgs,
|
|||
var process = new _TestLauncher();
|
||||
process.launch(pause_on_start,
|
||||
pause_on_exit,
|
||||
pause_on_unhandled_exceptions,
|
||||
trace_service).then((port) async {
|
||||
if (mainArgs.contains("--gdb")) {
|
||||
port = 8181;
|
||||
|
|
|
@ -1628,7 +1628,6 @@ void Debugger::SignalExceptionThrown(const Instance& exc) {
|
|||
// interested in exception events.
|
||||
if (ignore_breakpoints_ ||
|
||||
IsPaused() ||
|
||||
(!HasDebugEventHandler()) ||
|
||||
(exc_pause_info_ == kNoPauseOnExceptions)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -58,6 +58,9 @@ DEFINE_FLAG(bool, pause_isolates_on_start, false,
|
|||
"Pause isolates before starting.");
|
||||
DEFINE_FLAG(bool, pause_isolates_on_exit, false,
|
||||
"Pause isolates exiting.");
|
||||
DEFINE_FLAG(bool, pause_isolates_on_unhandled_exceptions, false,
|
||||
"Pause isolates on unhandled exceptions.");
|
||||
|
||||
DEFINE_FLAG(bool, break_at_isolate_spawn, false,
|
||||
"Insert a one-time breakpoint at the entrypoint for all spawned "
|
||||
"isolates");
|
||||
|
@ -1070,6 +1073,9 @@ bool Isolate::MakeRunnable() {
|
|||
if (!ServiceIsolate::IsServiceIsolate(this)) {
|
||||
message_handler()->set_pause_on_start(FLAG_pause_isolates_on_start);
|
||||
message_handler()->set_pause_on_exit(FLAG_pause_isolates_on_exit);
|
||||
if (FLAG_pause_isolates_on_unhandled_exceptions) {
|
||||
debugger()->SetExceptionPauseInfo(kPauseOnUnhandledExceptions);
|
||||
}
|
||||
}
|
||||
IsolateSpawnState* state = spawn_state();
|
||||
if (state != NULL) {
|
||||
|
|
Loading…
Reference in a new issue