blob: ec42f19ea600578590b3d98fbff9383b9ee5d592 [file] [log] [blame]
// 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.
import 'dart:async';
import 'package:async/async.dart';
import 'package:json_rpc_2/error_code.dart' as rpc_error;
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
import 'class.dart';
import 'exceptions.dart';
import 'object.dart';
import 'scope.dart';
import 'script.dart';
import 'sentinel.dart';
import 'source_location.dart';
import 'unresolved_source_location.dart';
import 'utils.dart';
VMBreakpoint newVMBreakpoint(Scope scope, Map json) {
if (json == null) return null;
assert(json["type"] == "Breakpoint");
return json["resolved"]
? new VMResolvedBreakpoint._(scope, json)
: new VMBreakpoint._(scope, json);
}
/// A debugger breakpoint.
///
/// A breakpoint corresponds to a location in the source file. Before the
/// isolate would execute that location, it pauses.
///
/// A breakpoint starts out unresolved, meaning that the exact location of the
/// breakpoint is imprecisely known because its script has not yet been fully
/// loaded. Once that script is fully loaded, the breakpoint is resolved and its
/// location is fully avaiable. A resolved breakpoint is always represented as a
/// [VMResolvedBreakpoint], no matter how it was loaded. The easiest way to wait
/// for a breakpoint to be resolved is by calling [loadResolved].
///
/// Unlike most [VMObject]s, this has no corresponding [VMObjectRef] type. The
/// full metadata is always available.
class VMBreakpoint extends VMObject {
final Scope _scope;
/// The ID for this breakpoint, which is unique relative to its isolate.
final String _id;
/// Whether [_id] is guaranteed to be the same for different VM service
/// instance objects that refer to the same breakpoint.
final bool _fixedId;
Uri get observatoryUrl => _scope.observatoryUrlFor(_id);
final int size;
final VMClassRef klass;
/// The number of this breakpoint.
///
/// This number is user-visible.
final int number;
/// The location of the breakpoint in the isolate's Dart source.
///
/// If this breakpoint is unresolved, this will be a
/// [VMUnresolvedSourceLocation]. Otherwise, it will be a [VMSourceLocation].
final VMBreakpointLocation _location;
VMBreakpointLocation get location => _location;
/// A stream that emits a copy of [this] each time it causes the isolate to
/// become paused.
Stream<VMBreakpoint> get onPause => _onPause;
Stream<VMBreakpoint> _onPause;
/// A future that fires when this breakpoint is removed.
///
/// If the breakpoint is already removed, this will complete immediately.
Future get onRemove => _onRemoveMemo.runOnce(() async {
await _scope.getInState(_scope.streams.debug, () async {
try {
await load();
return false;
} on VMSentinelException catch (_) {
return true;
}
}, (json) {
return json["kind"] == "BreakpointRemoved" &&
json["breakpoint"]["id"] == _id;
});
});
final _onRemoveMemo = new AsyncMemoizer();
VMBreakpoint._(Scope scope, Map json)
: _scope = scope,
_id = json["id"],
_fixedId = json["fixedId"] ?? false,
size = json["size"],
klass = newVMClassRef(scope, json["class"]),
number = json["breakpointNumber"],
_location = _newVMBreakpointLocation(scope, json["location"]) {
_onPause = transform(_scope.streams.debug, (json, sink) {
if (json["isolate"]["id"] != _scope.isolateId) return;
if (json["kind"] != "PauseBreakpoint") return;
for (var breakpoint in json["pauseBreakpoints"]) {
if (breakpoint["id"] != _id) continue;
sink.add(newVMBreakpoint(_scope, breakpoint));
break;
}
});
}
static VMBreakpointLocation _newVMBreakpointLocation(Scope scope, Map json) {
if (json == null) return null;
switch (json["type"]) {
case "SourceLocation": return newVMSourceLocation(scope, json);
case "UnresolvedSourceLocation":
return newVMUnresolvedSourceLocation(scope, json);
default:
throw new StateError(
'Unknown breakpoint location type ${json['type']}.');
}
}
Future<VMBreakpoint> load() async {
try {
return newVMBreakpoint(_scope, await _scope.loadObject(_id));
} on rpc.RpcException catch (error) {
if (error.code != rpc_error.INVALID_PARAMS) rethrow;
// Work around sdk#24247.
throw new VMSentinelException(VMSentinel.expired);
}
}
/// Reloads this breakpoint once it's resolved.
///
/// This will work whether this breakpoint is already resolved or has yet to
/// be resolved. However, it doesn't cause the breakpoint to be resolved.
Future<VMBreakpoint> loadResolved() {
return _scope.getInState(_scope.streams.debug, () async {
var breakpoint = await load();
return breakpoint is VMResolvedBreakpoint ? breakpoint : null;
}, (json) {
if (json["kind"] != "BreakpointResolved") return null;
if (json["breakpoint"]["id"] != _id) return null;
return newVMBreakpoint(_scope, json["breakpoint"]);
});
}
/// Removes this breakpoint.
Future remove() =>
_scope.sendRequest("removeBreakpoint", {"breakpointId": _id});
bool operator ==(other) => other is VMBreakpoint &&
(_fixedId ? _id == other._id : super == other);
int get hashCode => _fixedId ? _id.hashCode : super.hashCode;
String toString() => "breakpoint #$number in ${location.uri}";
}
/// A resolved breakpoint with a precise source location.
class VMResolvedBreakpoint extends VMBreakpoint {
VMSourceLocation get location => super.location as VMSourceLocation;
VMResolvedBreakpoint._(Scope scope, Map json)
: super._(scope, json) {
assert(super.location is VMSourceLocation);
}
Future<VMBreakpoint> loadResolved() => load();
}
/// The location of a breakpoint, whether or not it's resolved.
abstract class VMBreakpointLocation {
/// The URI of the script in which the breakpoint exists.
///
/// This is always set regardless of how resolved the breakpoint is.
Uri get uri;
/// The script the breakpoint is located in.
///
/// This may be `null` if the breakpoint is unresolved and the script hasn't
/// been loaded.
VMScriptRef get script;
/// The location of the breakpoint in [script].
///
/// This will be `null` if [script] is `null`. If the breakpoint is unresolved
/// but the script has been loaded, this will be the VM's best guess of the
/// correct token. If the breakpoint is resolved, it will be an exact
/// location.
VMScriptToken get token;
}