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:
John McCutchan 2016-01-26 10:12:46 -08:00
parent 82de52aae3
commit ec020a301b
5 changed files with 83 additions and 13 deletions

View file

@ -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);
});
}

View file

@ -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);

View file

@ -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;

View file

@ -1628,7 +1628,6 @@ void Debugger::SignalExceptionThrown(const Instance& exc) {
// interested in exception events.
if (ignore_breakpoints_ ||
IsPaused() ||
(!HasDebugEventHandler()) ||
(exc_pause_info_ == kNoPauseOnExceptions)) {
return;
}

View file

@ -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) {