├── .gitignore ├── .test_config ├── .travis.yml ├── AUTHORS ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── dart_test.yaml ├── lib ├── src │ ├── bound_field.dart │ ├── breakpoint.dart │ ├── class.dart │ ├── code.dart │ ├── context.dart │ ├── error.dart │ ├── exceptions.dart │ ├── field.dart │ ├── flag.dart │ ├── frame.dart │ ├── function.dart │ ├── instance.dart │ ├── isolate.dart │ ├── library.dart │ ├── message.dart │ ├── object.dart │ ├── pause_event.dart │ ├── scope.dart │ ├── script.dart │ ├── sentinel.dart │ ├── service_version.dart │ ├── source_location.dart │ ├── source_report.dart │ ├── stack.dart │ ├── stream_manager.dart │ ├── type_arguments.dart │ ├── unresolved_source_location.dart │ ├── utils.dart │ ├── v1_compatibility.dart │ └── vm.dart └── vm_service_client.dart ├── pubspec.yaml └── test ├── bound_field_test.dart ├── bound_variable_test.dart ├── breakpoint_test.dart ├── class_test.dart ├── client_test.dart ├── code_test.dart ├── error_test.dart ├── exception_handling_test.dart ├── field_test.dart ├── frame_test.dart ├── function_test.dart ├── instance_test.dart ├── isolate_test.dart ├── library_test.dart ├── message_test.dart ├── script_test.dart ├── source_report_test.dart ├── utils.dart └── vm_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .buildlog 2 | .DS_Store 3 | .idea 4 | .dart_tool/ 5 | .pub/ 6 | .settings/ 7 | build/ 8 | packages 9 | .packages 10 | pubspec.lock 11 | -------------------------------------------------------------------------------- /.test_config: -------------------------------------------------------------------------------- 1 | { 2 | "test_package": { 3 | "platforms": ["vm"] 4 | } 5 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | 3 | dart: 4 | - dev 5 | 6 | dart_task: 7 | # Tests are not passing at the moment – investigating 8 | #- test 9 | - dartanalyzer 10 | - dartfmt 11 | 12 | # Only building master means that we don't run two builds for each pull request. 13 | branches: 14 | only: [master] 15 | 16 | cache: 17 | directories: 18 | - $HOME/.pub-cache 19 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Google Inc. 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.6+3 2 | 3 | * Mark this package as discontinued. 4 | 5 | ## 0.2.6+2 6 | 7 | * Do not override path during `VMServiceClient.connect`. 8 | * Fixes issues with connecting to Dart `2.3` observatory URIs. 9 | 10 | ## 0.2.6+1 11 | 12 | * Allow `stream_channel` version 2.x 13 | 14 | ## 0.2.6 15 | 16 | * Add `VMPauseEvent.atAsyncSuspension` to indicate when an isolate is paused at an 17 | await, yield, or yield* statement (only available from VM service version 3.3). 18 | * Add `VMStep.OverAsyncSuspension` to allow continuing until execution returns from 19 | an await, yield, or yield* statement (only valid when 20 | `VMPauseEvent.atAsyncSuspension` is `true`). 21 | * Add `VMIsolate.setExceptionPauseMode` and `VMIsolate.exceptionPauseMode` to 22 | return/set pause behaviour for exceptions. 23 | 24 | ## 0.2.5+1 25 | 26 | * Support Dart 2 stable releases. 27 | 28 | ## 0.2.5 29 | 30 | * Update usage of SDK constants. 31 | 32 | * Increase minimum Dart SDK to `2.0.0-dev.58.0`. 33 | 34 | ## 0.2.4+3 35 | 36 | * Fix more Dart 2 runtime issues. 37 | 38 | ## 0.2.4+2 39 | 40 | * Fix type issues with Dart 2 runtime. 41 | 42 | ## 0.2.4+1 43 | 44 | * Updates to support Dart 2.0 core library changes (wave 45 | 2.2). See [issue 31847][sdk#31847] for details. 46 | 47 | [sdk#31847]: https://github.com/dart-lang/sdk/issues/31847 48 | 49 | ## 0.2.4 50 | 51 | * Internal changes only. 52 | 53 | ## 0.2.3 54 | 55 | * Add `VMIsolate.observatoryUrl` and `VMObjectRef.observatoryUrl` getters that 56 | provide access to human-friendly relative Observatory URLs for VM service objects. 57 | 58 | ## 0.2.2+4 59 | 60 | * Fix a bug where `Isolate.invokeExtension()` would fail if the extension method 61 | returned a non-`Map` value. 62 | 63 | ## 0.2.2+3 64 | 65 | * Fix strong-mode errors and warnings. 66 | 67 | ## 0.2.2+2 68 | 69 | * Narrow the dependency on `source_span`. 70 | 71 | ## 0.2.2+1 72 | 73 | * Fix some documentation comments. 74 | 75 | ## 0.2.2 76 | 77 | * Add `getSourceReport` to `VMIsolateRef` and `VMScriptRef`, which return a 78 | `VMSourceReport` for the target isolate or just the target script 79 | respectively. 80 | 81 | ## 0.2.1 82 | 83 | * `VMScriptToken.offset` is deprecated. This never returned the documented value 84 | in the first place, and in practice determining that value isn't possible from 85 | the information available in the token. 86 | 87 | * `VMScript.getLocation()` and `VMScript.getSpan()` now return spans with the 88 | correct line, column, and offset numbers. 89 | 90 | ## 0.2.0 91 | 92 | * **Breaking change**: `new VMServiceClient()` and `new 93 | VMServiceClient.withoutJson()` now take a `StreamChannel` rather than a 94 | `Stream`/`Sink` pair. 95 | 96 | * **Breaking change**: the static asynchronous factory 97 | `VMServiceClient.connect()` is now a synchronous constructor, `new 98 | VMServiceClient.connect()`. 99 | 100 | ## 0.1.3 101 | 102 | * On VM service versions 3.4 and greater, `VMIsolate.pauseEvent` now returns an 103 | instance of `VMNoneEvent` before the isolate is runnable. 104 | 105 | ## 0.1.2+1 106 | 107 | * Drop the dependency on the `crypto` package. 108 | 109 | ## 0.1.2 110 | 111 | * Add `VMIsolateRef.onExtensionEvent`, which emits events posted by VM service 112 | extensions using `postEvent` in `dart:developer`. 113 | 114 | * Add `VMIsolateRef.selectExtensionEvents()`, which selects events with specific 115 | kinds posted by VM service extensions using `postEvent` in `dart:developer`. 116 | 117 | * Add `VMIsolateRef.onExtensionAdded`, which emits an event when a VM service 118 | extension registers a new RPC. 119 | 120 | * Add `VMIsolateRef.waitForExtension()`, which returns when a given extension 121 | RPC is available. 122 | 123 | * Add `VMIsolateRef.invokeExtension()`, which invokes VM service extension RPCs 124 | registered using `registerExtension` in `dart:developer`. 125 | 126 | * Add `VMIsolate.extensionRpcs`, which returns the extension RPCs registered in 127 | a given isolate. 128 | 129 | ## 0.1.1+1 130 | 131 | * Fix a bug where `VMPauseEvent.time` would always be reported as `null` or 132 | crash. 133 | 134 | ## 0.1.1 135 | 136 | * Fix support for VM service protocol 1.0 events. 137 | 138 | ## 0.1.0 139 | 140 | * Initial version. 141 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at 2 | the end). 3 | 4 | ### Before you contribute 5 | Before we can use your code, you must sign the 6 | [Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) 7 | (CLA), which you can do online. The CLA is necessary mainly because you own the 8 | copyright to your changes, even after your contribution becomes part of our 9 | codebase, so we need your permission to use and distribute your code. We also 10 | need to be sure of various other things—for instance that you'll tell us if you 11 | know that your code infringes on other people's patents. You don't have to sign 12 | the CLA until after you've submitted your code for review and a member has 13 | approved it, but you must do it before we can put your code into our codebase. 14 | 15 | Before you start working on a larger contribution, you should get in touch with 16 | us first through the issue tracker with your idea so that we can help out and 17 | possibly guide you. Coordinating up front makes it much easier to avoid 18 | frustration later on. 19 | 20 | ### Code reviews 21 | All submissions, including submissions by project members, require review. 22 | 23 | ### File headers 24 | All files in the project must start with the following header. 25 | 26 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 27 | // for details. All rights reserved. Use of this source code is governed by a 28 | // BSD-style license that can be found in the LICENSE file. 29 | 30 | ### The small print 31 | Contributions made by corporations are covered by a different agreement than the 32 | one above, the 33 | [Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate). 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015, the Dart project authors. All rights reserved. 2 | Redistribution and use in source and binary forms, with or without 3 | modification, are permitted provided that the following conditions are 4 | met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This package is **DISCONTINUED**. 2 | Use [package:vm_service](https://pub.dev/packages/vm_service) instead. 3 | 4 | A client for the [Dart VM service][service api]. 5 | 6 | [service api]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md 7 | -------------------------------------------------------------------------------- /dart_test.yaml: -------------------------------------------------------------------------------- 1 | # The VM service itself is a little flaky, so we retry failing tests a few times 2 | # to work around it. 3 | retry: 3 4 | -------------------------------------------------------------------------------- /lib/src/bound_field.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'field.dart'; 6 | import 'instance.dart'; 7 | import 'scope.dart'; 8 | 9 | VMBoundField newVMBoundField(Scope scope, Map json) { 10 | if (json == null) return null; 11 | return new VMBoundField._(scope, json); 12 | } 13 | 14 | /// An instance field bound to a particular value. 15 | class VMBoundField { 16 | /// The declaration of this field. 17 | final VMFieldRef declaration; 18 | 19 | /// The value that the field is bound to. 20 | /// 21 | /// If this field is uninitialized, this will be [VMSentinel.notInitialized]. 22 | /// If it's currently being initialized, it will be 23 | /// [VMSentinel.beingInitialized]. Otherwise, it will be a [VMInstanceRef]. 24 | final value; 25 | 26 | VMBoundField._(Scope scope, Map json) 27 | : declaration = newVMFieldRef(scope, json["decl"]), 28 | value = newVMInstanceRefOrSentinel(scope, json["value"]); 29 | 30 | String toString() => "${declaration.description} = $value"; 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/breakpoint.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:async/async.dart'; 8 | import 'package:json_rpc_2/error_code.dart' as rpc_error; 9 | import 'package:json_rpc_2/json_rpc_2.dart' as rpc; 10 | 11 | import 'class.dart'; 12 | import 'exceptions.dart'; 13 | import 'object.dart'; 14 | import 'scope.dart'; 15 | import 'script.dart'; 16 | import 'sentinel.dart'; 17 | import 'source_location.dart'; 18 | import 'unresolved_source_location.dart'; 19 | import 'utils.dart'; 20 | 21 | VMBreakpoint newVMBreakpoint(Scope scope, Map json) { 22 | if (json == null) return null; 23 | assert(json["type"] == "Breakpoint"); 24 | return json["resolved"] 25 | ? new VMResolvedBreakpoint._(scope, json) 26 | : new VMBreakpoint._(scope, json); 27 | } 28 | 29 | /// A debugger breakpoint. 30 | /// 31 | /// A breakpoint corresponds to a location in the source file. Before the 32 | /// isolate would execute that location, it pauses. 33 | /// 34 | /// A breakpoint starts out unresolved, meaning that the exact location of the 35 | /// breakpoint is imprecisely known because its script has not yet been fully 36 | /// loaded. Once that script is fully loaded, the breakpoint is resolved and its 37 | /// location is fully avaiable. A resolved breakpoint is always represented as a 38 | /// [VMResolvedBreakpoint], no matter how it was loaded. The easiest way to wait 39 | /// for a breakpoint to be resolved is by calling [loadResolved]. 40 | /// 41 | /// Unlike most [VMObject]s, this has no corresponding [VMObjectRef] type. The 42 | /// full metadata is always available. 43 | class VMBreakpoint extends VMObject { 44 | final Scope _scope; 45 | 46 | /// The ID for this breakpoint, which is unique relative to its isolate. 47 | final String _id; 48 | 49 | /// Whether [_id] is guaranteed to be the same for different VM service 50 | /// instance objects that refer to the same breakpoint. 51 | final bool _fixedId; 52 | 53 | Uri get observatoryUrl => _scope.observatoryUrlFor(_id); 54 | 55 | final int size; 56 | 57 | final VMClassRef klass; 58 | 59 | /// The number of this breakpoint. 60 | /// 61 | /// This number is user-visible. 62 | final int number; 63 | 64 | /// The location of the breakpoint in the isolate's Dart source. 65 | /// 66 | /// If this breakpoint is unresolved, this will be a 67 | /// [VMUnresolvedSourceLocation]. Otherwise, it will be a [VMSourceLocation]. 68 | final VMBreakpointLocation _location; 69 | VMBreakpointLocation get location => _location; 70 | 71 | /// A stream that emits a copy of [this] each time it causes the isolate to 72 | /// become paused. 73 | Stream get onPause => _onPause; 74 | Stream _onPause; 75 | 76 | /// A future that fires when this breakpoint is removed. 77 | /// 78 | /// If the breakpoint is already removed, this will complete immediately. 79 | Future get onRemove => _onRemoveMemo.runOnce(() async { 80 | await _scope.getInState(_scope.streams.debug, () async { 81 | try { 82 | await load(); 83 | return false; 84 | } on VMSentinelException catch (_) { 85 | return true; 86 | } 87 | }, (json) { 88 | return json["kind"] == "BreakpointRemoved" && 89 | json["breakpoint"]["id"] == _id; 90 | }); 91 | }); 92 | final _onRemoveMemo = new AsyncMemoizer(); 93 | 94 | VMBreakpoint._(Scope scope, Map json) 95 | : _scope = scope, 96 | _id = json["id"], 97 | _fixedId = json["fixedId"] ?? false, 98 | size = json["size"], 99 | klass = newVMClassRef(scope, json["class"]), 100 | number = json["breakpointNumber"], 101 | _location = _newVMBreakpointLocation(scope, json["location"]) { 102 | _onPause = transform(_scope.streams.debug, (json, sink) { 103 | if (json["isolate"]["id"] != _scope.isolateId) return; 104 | if (json["kind"] != "PauseBreakpoint") return; 105 | 106 | for (var breakpoint in json["pauseBreakpoints"]) { 107 | if (breakpoint["id"] != _id) continue; 108 | sink.add(newVMBreakpoint(_scope, breakpoint)); 109 | break; 110 | } 111 | }); 112 | } 113 | 114 | static VMBreakpointLocation _newVMBreakpointLocation(Scope scope, Map json) { 115 | if (json == null) return null; 116 | switch (json["type"]) { 117 | case "SourceLocation": 118 | return newVMSourceLocation(scope, json); 119 | case "UnresolvedSourceLocation": 120 | return newVMUnresolvedSourceLocation(scope, json); 121 | default: 122 | throw new StateError( 123 | 'Unknown breakpoint location type ${json['type']}.'); 124 | } 125 | } 126 | 127 | Future load() async { 128 | try { 129 | return newVMBreakpoint(_scope, await _scope.loadObject(_id)); 130 | } on rpc.RpcException catch (error) { 131 | if (error.code != rpc_error.INVALID_PARAMS) rethrow; 132 | 133 | // Work around sdk#24247. 134 | throw new VMSentinelException(VMSentinel.expired); 135 | } 136 | } 137 | 138 | /// Reloads this breakpoint once it's resolved. 139 | /// 140 | /// This will work whether this breakpoint is already resolved or has yet to 141 | /// be resolved. However, it doesn't cause the breakpoint to be resolved. 142 | Future loadResolved() { 143 | return _scope.getInState(_scope.streams.debug, () async { 144 | var breakpoint = await load(); 145 | return breakpoint is VMResolvedBreakpoint ? breakpoint : null; 146 | }, (json) { 147 | if (json["kind"] != "BreakpointResolved") return null; 148 | if (json["breakpoint"]["id"] != _id) return null; 149 | return newVMBreakpoint(_scope, json["breakpoint"]); 150 | }); 151 | } 152 | 153 | /// Removes this breakpoint. 154 | Future remove() => 155 | _scope.sendRequest("removeBreakpoint", {"breakpointId": _id}); 156 | 157 | bool operator ==(other) => 158 | other is VMBreakpoint && (_fixedId ? _id == other._id : super == other); 159 | 160 | int get hashCode => _fixedId ? _id.hashCode : super.hashCode; 161 | 162 | String toString() => "breakpoint #$number in ${location.uri}"; 163 | } 164 | 165 | /// A resolved breakpoint with a precise source location. 166 | class VMResolvedBreakpoint extends VMBreakpoint { 167 | VMSourceLocation get location => super.location as VMSourceLocation; 168 | 169 | VMResolvedBreakpoint._(Scope scope, Map json) : super._(scope, json) { 170 | assert(super.location is VMSourceLocation); 171 | } 172 | 173 | Future loadResolved() => load(); 174 | } 175 | 176 | /// The location of a breakpoint, whether or not it's resolved. 177 | abstract class VMBreakpointLocation { 178 | /// The URI of the script in which the breakpoint exists. 179 | /// 180 | /// This is always set regardless of how resolved the breakpoint is. 181 | Uri get uri; 182 | 183 | /// The script the breakpoint is located in. 184 | /// 185 | /// This may be `null` if the breakpoint is unresolved and the script hasn't 186 | /// been loaded. 187 | VMScriptRef get script; 188 | 189 | /// The location of the breakpoint in [script]. 190 | /// 191 | /// This will be `null` if [script] is `null`. If the breakpoint is unresolved 192 | /// but the script has been loaded, this will be the VM's best guess of the 193 | /// correct token. If the breakpoint is resolved, it will be an exact 194 | /// location. 195 | VMScriptToken get token; 196 | } 197 | -------------------------------------------------------------------------------- /lib/src/class.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:collection'; 7 | 8 | import 'error.dart'; 9 | import 'field.dart'; 10 | import 'function.dart'; 11 | import 'instance.dart'; 12 | import 'library.dart'; 13 | import 'object.dart'; 14 | import 'scope.dart'; 15 | import 'source_location.dart'; 16 | 17 | VMClassRef newVMClassRef(Scope scope, Map json) { 18 | if (json == null) return null; 19 | assert(json["type"] == "@Class" || json["type"] == "Class"); 20 | return new VMClassRef._(scope, json); 21 | } 22 | 23 | /// A reference to a Dart class. 24 | class VMClassRef implements VMObjectRef { 25 | final Scope _scope; 26 | 27 | /// The ID for this class, which is unique relative to its isolate. 28 | final String _id; 29 | 30 | /// Whether [_id] is guaranteed to be the same for different VM service class 31 | /// objects that refer to the same class. 32 | final bool _fixedId; 33 | 34 | Uri get observatoryUrl => _scope.observatoryUrlFor(_id); 35 | 36 | /// The name of this class. 37 | final String name; 38 | 39 | VMClassRef._(this._scope, Map json) 40 | : _id = json["id"], 41 | _fixedId = json["fixedId"] ?? false, 42 | name = json["name"]; 43 | 44 | Future load() async => 45 | new VMClass._(_scope, await _scope.loadObject(_id)); 46 | 47 | /// Evaluates [expression] in the context of this class. 48 | /// 49 | /// Throws a [VMErrorException] if evaluating the expression throws an error. 50 | Future evaluate(String expression) => 51 | _scope.evaluate(_id, expression); 52 | 53 | bool operator ==(other) => 54 | other is VMClassRef && (_fixedId ? _id == other._id : super == other); 55 | 56 | int get hashCode => _fixedId ? _id.hashCode : super.hashCode; 57 | 58 | String toString() => name; 59 | } 60 | 61 | /// A Dart class. 62 | class VMClass extends VMClassRef implements VMObject { 63 | final VMClassRef klass; 64 | 65 | final int size; 66 | 67 | /// The error that occurred during class finalization, or `null` if no error 68 | /// occurred. 69 | final VMErrorRef error; 70 | 71 | /// Whether this class is abstract. 72 | final bool isAbstract; 73 | 74 | /// Whether this class is const. 75 | final bool isConst; 76 | 77 | /// The library that contains this class. 78 | final VMLibraryRef library; 79 | 80 | /// The location of this class in the isolate's source code. 81 | final VMSourceLocation location; 82 | 83 | /// The superclass of this class, or `null` if it has no superclass. 84 | final VMClassRef superclass; 85 | 86 | /// This class's interface types. 87 | final List interfaces; 88 | 89 | /// The fields defined in this class, indexed by name. 90 | /// 91 | /// This doesn't include fields from superclasses. 92 | final Map fields; 93 | 94 | /// The functions defined in this class, indexed by name. 95 | /// 96 | /// This includes both static and instance functions, but doesn't include 97 | /// functions from superclasses. 98 | final Map functions; 99 | 100 | /// This class's subclasses. 101 | final List subclasses; 102 | 103 | VMClass._(Scope scope, Map json) 104 | : klass = new VMClassRef._(scope, json["class"]), 105 | size = json["size"], 106 | error = newVMErrorRef(scope, json["error"]), 107 | isAbstract = json["abstract"], 108 | isConst = json["const"], 109 | library = newVMLibraryRef(scope, json["library"]), 110 | location = newVMSourceLocation(scope, json["location"]), 111 | superclass = newVMClassRef(scope, json["super"]), 112 | interfaces = new List.unmodifiable(json["interfaces"].map( 113 | (interfaceJson) => newVMTypeInstanceRef(scope, interfaceJson))), 114 | fields = new UnmodifiableMapView(new Map.fromIterable(json["fields"], 115 | key: (field) => field["name"], 116 | value: (field) => newVMFieldRef(scope, field))), 117 | functions = new UnmodifiableMapView(new Map.fromIterable( 118 | json["functions"], 119 | key: (function) => function["name"], 120 | value: (function) => newVMFunctionRef(scope, function))), 121 | subclasses = new List.unmodifiable(json["subclasses"] 122 | .map((subclass) => newVMClassRef(scope, subclass))), 123 | super._(scope, json); 124 | } 125 | -------------------------------------------------------------------------------- /lib/src/code.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'class.dart'; 8 | import 'object.dart'; 9 | import 'scope.dart'; 10 | 11 | VMCodeRef newVMCodeRef(Scope scope, Map json) { 12 | if (json == null) return null; 13 | assert(json["type"] == "@Code" || json["type"] == "Code"); 14 | return new VMCodeRef._(scope, json); 15 | } 16 | 17 | /// A reference to compiled code in the Dart VM. 18 | class VMCodeRef implements VMObjectRef { 19 | final Scope _scope; 20 | 21 | /// The ID for this code, which is unique relative to its isolate. 22 | final String _id; 23 | 24 | /// Whether [_id] is guaranteed to be the same for different VM service code 25 | /// objects that refer to the same code. 26 | final bool _fixedId; 27 | 28 | Uri get observatoryUrl => _scope.observatoryUrlFor(_id); 29 | 30 | /// This code's name. 31 | final String name; 32 | 33 | /// The type of code this is. 34 | final VMCodeKind kind; 35 | 36 | VMCodeRef._(this._scope, Map json) 37 | : _id = json["id"], 38 | _fixedId = json["fixedId"] ?? false, 39 | name = json["name"], 40 | kind = new VMCodeKind._parse(json["kind"]); 41 | 42 | Future load() async => 43 | new VMCode._(_scope, await _scope.loadObject(_id)); 44 | 45 | bool operator ==(other) => 46 | other is VMCodeRef && (_fixedId ? _id == other._id : super == other); 47 | 48 | int get hashCode => _fixedId ? _id.hashCode : super.hashCode; 49 | 50 | String toString() { 51 | // For stubs, the name already includes "[Stub]". 52 | if (kind == VMCodeKind.stub) return name; 53 | return "$name ($kind)"; 54 | } 55 | } 56 | 57 | /// Compiled code in the Dart VM. 58 | class VMCode extends VMCodeRef implements VMObject { 59 | final VMClassRef klass; 60 | 61 | final int size; 62 | 63 | VMCode._(Scope scope, Map json) 64 | : klass = newVMClassRef(scope, json["class"]), 65 | size = json["size"], 66 | super._(scope, json); 67 | } 68 | 69 | /// An enum of types of code. 70 | class VMCodeKind { 71 | /// User-authored Dart code. 72 | static const dart = const VMCodeKind._("Dart"); 73 | 74 | /// Native C++ code. 75 | static const native = const VMCodeKind._("Native"); 76 | 77 | /// A stub that hasn't yet been parsed compiled. 78 | static const stub = const VMCodeKind._("Stub"); 79 | 80 | static const tag = const VMCodeKind._("Tag"); 81 | 82 | /// Code that's been garbage-collected. 83 | static const collected = const VMCodeKind._("Collected"); 84 | 85 | /// The name of the kind of code. 86 | final String name; 87 | 88 | factory VMCodeKind._parse(String name) { 89 | switch (name) { 90 | case "Dart": 91 | return VMCodeKind.dart; 92 | case "Native": 93 | return VMCodeKind.native; 94 | case "Stub": 95 | return VMCodeKind.stub; 96 | case "Tag": 97 | return VMCodeKind.tag; 98 | case "Collected": 99 | return VMCodeKind.collected; 100 | default: 101 | throw new StateError("Unknown VM code kind \"$name\"."); 102 | } 103 | } 104 | 105 | const VMCodeKind._(this.name); 106 | 107 | String toString() => name; 108 | } 109 | -------------------------------------------------------------------------------- /lib/src/context.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'class.dart'; 8 | import 'instance.dart'; 9 | import 'object.dart'; 10 | import 'scope.dart'; 11 | 12 | VMContextRef newVMContextRef(Scope scope, Map json) { 13 | if (json == null) return null; 14 | assert(json["type"] == "@Context" || json["type"] == "Context"); 15 | return new VMContextRef._(scope, json); 16 | } 17 | 18 | /// A reference to the captured variables for a closure. 19 | class VMContextRef implements VMObjectRef { 20 | final Scope _scope; 21 | 22 | /// The ID for this context, which is unique relative to its isolate. 23 | final String _id; 24 | 25 | /// Whether [_id] is guaranteed to be the same for different VM service 26 | /// context objects that refer to the same context. 27 | final bool _fixedId; 28 | 29 | Uri get observatoryUrl => _scope.observatoryUrlFor(_id); 30 | 31 | /// The number of variables in this context. 32 | final int length; 33 | 34 | VMContextRef._(Scope scope, Map json) 35 | : _scope = scope, 36 | _id = json["id"], 37 | _fixedId = json["fixedId"] ?? false, 38 | length = json["length"]; 39 | 40 | Future load() async => 41 | new VMContext._(_scope, await _scope.loadObject(_id)); 42 | 43 | bool operator ==(other) => 44 | other is VMContextRef && (_fixedId ? _id == other._id : super == other); 45 | 46 | int get hashCode => _fixedId ? _id.hashCode : super.hashCode; 47 | } 48 | 49 | /// The captured variables for a closure. 50 | class VMContext extends VMContextRef implements VMObject { 51 | final int size; 52 | 53 | final VMClassRef klass; 54 | 55 | /// The enclosing context, or `null`. 56 | final VMContextRef parent; 57 | 58 | /// The variables in this context. 59 | /// 60 | /// Each element is either a [VMInstanceRef] or a [VMSentinel]. 61 | final List variables; 62 | 63 | VMContext._(Scope scope, Map json) 64 | : size = json["size"], 65 | klass = newVMClassRef(scope, json["class"]), 66 | parent = newVMContextRef(scope, json["parent"]), 67 | variables = new List.unmodifiable(json["variables"].map((variable) => 68 | newVMInstanceRefOrSentinel(scope, variable["value"]))), 69 | super._(scope, json); 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/error.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:stack_trace/stack_trace.dart'; 8 | 9 | import 'class.dart'; 10 | import 'instance.dart'; 11 | import 'object.dart'; 12 | import 'scope.dart'; 13 | 14 | VMErrorRef newVMErrorRef(Scope scope, Map json) { 15 | if (json == null) return null; 16 | assert(json["type"] == "@Error" || json["type"] == "Error"); 17 | return new VMErrorRef._(scope, json); 18 | } 19 | 20 | VMErrorRef newVMError(Scope scope, Map json) { 21 | if (json == null) return null; 22 | assert(json["type"] == "Error"); 23 | return new VMError._(scope, json); 24 | } 25 | 26 | /// A reference to a Dart language error. 27 | class VMErrorRef implements VMObjectRef { 28 | final Scope _scope; 29 | 30 | /// The ID for the error, which is unique relative to its isolate. 31 | final String _id; 32 | 33 | /// Whether [_id] is guaranteed to be the same for different VM service error 34 | /// objects that refer to the same error. 35 | final bool _fixedId; 36 | 37 | Uri get observatoryUrl => _scope.observatoryUrlFor(_id); 38 | 39 | /// The kind of error this is. 40 | final VMErrorKind kind; 41 | 42 | /// The error message. 43 | final String message; 44 | 45 | VMErrorRef._(this._scope, Map json) 46 | : _id = json["id"], 47 | _fixedId = json["fixedId"] ?? false, 48 | kind = new VMErrorKind._parse(json["kind"]), 49 | message = json["message"]; 50 | 51 | Future load() async => 52 | new VMError._(_scope, await _scope.loadObject(_id)); 53 | 54 | bool operator ==(other) => 55 | other is VMErrorRef && (_fixedId ? _id == other._id : super == other); 56 | 57 | int get hashCode => _fixedId ? _id.hashCode : super.hashCode; 58 | 59 | String toString() => "$kind: $message"; 60 | } 61 | 62 | /// A Dart language error. 63 | class VMError extends VMErrorRef implements VMObject { 64 | final VMClassRef klass; 65 | 66 | final int size; 67 | 68 | /// The exception that caused the error. 69 | /// 70 | /// Note that for asynchonous errors prior to VM service version 3.0, this 71 | /// may be a wrapped instance of [AsyncError]. 72 | final VMInstanceRef exception; 73 | 74 | /// The stack trace for the error. 75 | /// 76 | /// This can be accessed as a local trace using [getTrace]. 77 | /// 78 | /// Note that for asynchonous errors prior to VM service version 3.0, 79 | /// [exception] may be an instance of [AsyncError], in which case that error 80 | /// contains the original stack trace. 81 | final VMInstanceRef stackTrace; 82 | 83 | VMError._(Scope scope, Map json) 84 | : klass = newVMClassRef(scope, json["class"]), 85 | size = json["size"], 86 | exception = newVMInstanceRef(scope, json["exception"]), 87 | stackTrace = newVMInstanceRef(scope, json["stacktrace"]), 88 | super._(scope, json); 89 | 90 | /// Loads the error's stack trace as a concrete Dart stack trace. 91 | /// 92 | /// If the original stack trace was a [Chain], this returns a [Chain]. 93 | /// Otherwise, it returns a [Trace]. 94 | Future getTrace() async { 95 | var vmTrace = stackTrace; 96 | if (exception.klass.name == '_UncaughtAsyncError') { 97 | vmTrace = (await exception.load()).fields['stackTrace'].value; 98 | } 99 | 100 | if (vmTrace is VMStackTraceInstanceRef) return vmTrace.value; 101 | 102 | VMStringInstanceRef contents = await vmTrace.evaluate("toString()"); 103 | if (contents.isValueTruncated) contents = await contents.load(); 104 | return new Chain.parse(contents.value); 105 | } 106 | } 107 | 108 | /// An enum of different kinds of Dart errors. 109 | class VMErrorKind { 110 | /// A Dart exception with no handler. 111 | static const unhandledException = const VMErrorKind._("UnhandledException"); 112 | 113 | /// An error caused by invalid Dart code. 114 | static const languageError = const VMErrorKind._("LanguageError"); 115 | 116 | /// An internal error. 117 | /// 118 | /// These errors should not be exposed—if seen, they should be [reported as 119 | /// bugs](https://github.com/dart-lang/sdk/issues/new). 120 | static const internalError = const VMErrorKind._("InternalError"); 121 | 122 | /// The isolate has been terminated from an external source. 123 | static const terminationError = const VMErrorKind._("TerminationError"); 124 | 125 | /// The name of the error. 126 | final String name; 127 | 128 | /// Parses the error from its service protocol name. 129 | factory VMErrorKind._parse(String name) { 130 | switch (name) { 131 | case "UnhandledException": 132 | return VMErrorKind.unhandledException; 133 | case "LanguageError": 134 | return VMErrorKind.languageError; 135 | case "InternalError": 136 | return VMErrorKind.internalError; 137 | case "TerminationError": 138 | return VMErrorKind.terminationError; 139 | default: 140 | throw new StateError("Unknown VM error kind \"$name\"."); 141 | } 142 | } 143 | 144 | const VMErrorKind._(this.name); 145 | 146 | String toString() => name; 147 | } 148 | -------------------------------------------------------------------------------- /lib/src/exceptions.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'error.dart'; 6 | import 'sentinel.dart'; 7 | import 'service_version.dart'; 8 | 9 | /// An exception thrown when the client attempts to load a remote object that's 10 | /// no longer available. 11 | class VMSentinelException implements Exception { 12 | /// The sentinel indicating what happened to the remote object. 13 | final VMSentinel sentinel; 14 | 15 | VMSentinelException(this.sentinel); 16 | 17 | String toString() => "Unexpected $sentinel sentinel."; 18 | } 19 | 20 | /// An exception that represents a Dart exception in the remote VM. 21 | class VMErrorException implements Exception { 22 | /// The error in the remote VM. 23 | final VMErrorRef error; 24 | 25 | VMErrorException(this.error); 26 | 27 | String toString() => "Remote VM ${error.kind}: ${error.message}"; 28 | } 29 | 30 | /// An exception indicating that the VM service client doesn't support the 31 | /// current protocol version. 32 | class VMUnsupportedVersionException implements Exception { 33 | /// The version reported by the VM service protocol. 34 | /// 35 | /// This may be `null` if the reported version isn't in a format the client 36 | /// understands. 37 | final VMServiceVersion version; 38 | 39 | VMUnsupportedVersionException([this.version]); 40 | 41 | String toString() => version == null 42 | ? "The VM service protocol is an unsupported version." 43 | : "The VM service protocol is unsupported version $version."; 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/field.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'class.dart'; 8 | import 'instance.dart'; 9 | import 'library.dart'; 10 | import 'object.dart'; 11 | import 'scope.dart'; 12 | import 'source_location.dart'; 13 | 14 | VMFieldRef newVMFieldRef(Scope scope, Map json) { 15 | if (json == null) return null; 16 | assert(json["type"] == "@Field" || json["type"] == "Field"); 17 | return new VMFieldRef._(scope, json); 18 | } 19 | 20 | /// A reference to a field or variable. 21 | class VMFieldRef implements VMObjectRef { 22 | final Scope _scope; 23 | 24 | /// The ID for this field, which is unique relative to its isolate. 25 | final String _id; 26 | 27 | /// Whether [_id] is guaranteed to be the same for different VM service field 28 | /// objects that refer to the same field. 29 | final bool _fixedId; 30 | 31 | Uri get observatoryUrl => _scope.observatoryUrlFor(_id); 32 | 33 | /// The name of this field. 34 | final String name; 35 | 36 | /// The owner of this field. 37 | /// 38 | /// This is either a [VMLibraryRef] or a [VMClassRef]. 39 | final VMObjectRef owner; 40 | 41 | /// The declared type of this field. 42 | final VMTypeLikeInstanceRef declaredType; 43 | 44 | /// Whether this is a const field. 45 | final bool isConst; 46 | 47 | /// Whether this is a final field. 48 | final bool isFinal; 49 | 50 | /// Whether this is a static field. 51 | final bool isStatic; 52 | 53 | /// The Dart declaration of this field, excluding its value. 54 | String get description { 55 | var buffer = new StringBuffer(); 56 | if (isStatic && owner is! VMLibraryRef) buffer.write("static "); 57 | if (isConst) { 58 | buffer.write("const "); 59 | } else if (isFinal) { 60 | buffer.write("final "); 61 | } 62 | if (declaredType is! VMTypeInstanceRef || 63 | (declaredType as VMTypeInstanceRef).name != "dynamic") { 64 | buffer.write("$declaredType "); 65 | } else if (!isConst && !isFinal) { 66 | buffer.write("var "); 67 | } 68 | buffer.write(name); 69 | return buffer.toString(); 70 | } 71 | 72 | VMFieldRef._(Scope scope, Map json) 73 | : _scope = scope, 74 | _id = json["id"], 75 | _fixedId = json["fixedId"] ?? false, 76 | name = json["name"], 77 | owner = _newLibraryOrClassRef(scope, json["owner"]), 78 | declaredType = newVMTypeLikeInstanceRef(scope, json["declaredType"]), 79 | isConst = json["const"], 80 | isFinal = json["final"], 81 | isStatic = json["static"]; 82 | 83 | /// Parses and returns either a [VMLibraryRef] or [VMClassRef]. 84 | static VMObjectRef _newLibraryOrClassRef(Scope scope, Map json) { 85 | if (json == null) return null; 86 | switch (json["type"]) { 87 | case "@Library": 88 | return newVMLibraryRef(scope, json); 89 | case "@Class": 90 | return newVMClassRef(scope, json); 91 | default: 92 | throw new StateError('Unexpected Object type "${json["type"]}".'); 93 | } 94 | } 95 | 96 | Future load() async => 97 | new VMField._(_scope, await _scope.loadObject(_id)); 98 | 99 | bool operator ==(other) => 100 | other is VMFieldRef && (_fixedId ? _id == other._id : super == other); 101 | 102 | int get hashCode => _fixedId ? _id.hashCode : super.hashCode; 103 | 104 | String toString() => "$description = ..."; 105 | } 106 | 107 | /// A field or variable. 108 | class VMField extends VMFieldRef implements VMObject { 109 | final VMClassRef klass; 110 | 111 | final int size; 112 | 113 | /// The value of this field. 114 | /// 115 | /// This will be `null` unless this is a static field. The values of 116 | /// non-static fields are available through [VMInstance.fields]. 117 | final VMInstanceRef value; 118 | 119 | /// The lcoation of this field in the source code. 120 | final VMSourceLocation location; 121 | 122 | VMField._(Scope scope, Map json) 123 | : klass = newVMClassRef(scope, json["class"]), 124 | size = json["size"], 125 | value = newVMInstanceRef(scope, json["staticValue"]), 126 | location = newVMSourceLocation(scope, json["location"]), 127 | super._(scope, json); 128 | 129 | String toString() => "$description = ${value ?? '...'}"; 130 | } 131 | -------------------------------------------------------------------------------- /lib/src/flag.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:collection/collection.dart'; 6 | 7 | List newVMFlagList(Map json) { 8 | if (json == null) return null; 9 | return new _FlagList(json); 10 | } 11 | 12 | /// A delegating list that improves the `toString()` to only include modified 13 | /// flags. 14 | class _FlagList extends DelegatingList { 15 | _FlagList(Map json) 16 | : super( 17 | (json["flags"] as List).map((flag) => new VMFlag._(flag)).toList()); 18 | 19 | String toString() { 20 | return "[" + super.where((flag) => flag.modified).join(", ") + ", ...]"; 21 | } 22 | } 23 | 24 | /// A flag passed to the VM. 25 | class VMFlag { 26 | /// The name of the flag. 27 | final String name; 28 | 29 | /// A short description of the flag. 30 | final String comment; 31 | 32 | /// Whether the flag has been modified from its default setting. 33 | /// 34 | /// As of VM service version 3.0, this reflects the VM's internal notion of 35 | /// whether the flag has been set, which may be different than whether the 36 | /// user set them explicitly. 37 | final bool modified; 38 | 39 | /// The value passed to the flag, or `null` if it has no value. 40 | final String value; 41 | 42 | VMFlag._(Map json) 43 | : name = json["name"], 44 | comment = json["comment"], 45 | modified = json["modified"], 46 | value = json["valueAsString"]; 47 | 48 | String toString() { 49 | var result = "--$name"; 50 | if (value != null) result += "=$value"; 51 | return result; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/frame.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:collection'; 7 | 8 | import 'package:stack_trace/stack_trace.dart'; 9 | 10 | import 'code.dart'; 11 | import 'error.dart'; 12 | import 'exceptions.dart'; 13 | import 'function.dart'; 14 | import 'instance.dart'; 15 | import 'scope.dart'; 16 | import 'source_location.dart'; 17 | import 'utils.dart'; 18 | 19 | VMFrame newVMFrame(Scope scope, Map json) { 20 | if (json == null) return null; 21 | assert(json["type"] == "Frame"); 22 | return new VMFrame._(scope, json); 23 | } 24 | 25 | /// An active stack frame. 26 | class VMFrame { 27 | final Scope _scope; 28 | 29 | /// The index of the frame in [VMStack.frames]. 30 | /// 31 | /// The lower the index, the closer the frame to the point of execution. The 32 | /// actual point of execution has index 0. 33 | final int index; 34 | 35 | /// The function containing the frame. 36 | final VMFunctionRef function; 37 | 38 | /// The frame's code. 39 | final VMCodeRef code; 40 | 41 | /// The location of the frame in Dart source. 42 | final VMSourceLocation location; 43 | 44 | /// The local variables in the current frame, indexed by name. 45 | final Map variables; 46 | 47 | VMFrame._(Scope scope, Map json) 48 | : _scope = scope, 49 | index = json["index"], 50 | function = newVMFunctionRef(scope, json["function"]), 51 | code = newVMCodeRef(scope, json["code"]), 52 | location = newVMSourceLocation(scope, json["location"]), 53 | variables = new UnmodifiableMapView(new Map.fromIterable(json["vars"], 54 | key: (variable) => variable["name"], 55 | value: (variable) => new VMBoundVariable._(scope, variable))); 56 | 57 | /// Evaluates [expression] in the context of this frame. 58 | /// 59 | /// Throws a [VMErrorException] if evaluating the expression throws an error. 60 | /// Throws a [VMSentinelException] if this frame has expired. 61 | Future evaluate(String expression) async { 62 | var result = await _scope.sendRequest( 63 | "evaluateInFrame", {"frameIndex": index, "expression": expression}); 64 | 65 | switch (result["type"]) { 66 | case "@Error": 67 | throw new VMErrorException(newVMErrorRef(_scope, result)); 68 | case "@Instance": 69 | return newVMInstanceRef(_scope, result); 70 | default: 71 | throw new StateError('Unexpected Object type "${result["type"]}".'); 72 | } 73 | } 74 | 75 | /// Loads a `stack_trace` [Frame] that corresponds to this frame. 76 | Future getFrame() async => frameToFrame(this); 77 | 78 | String toString() => "#$index in $function"; 79 | } 80 | 81 | /// A local variable bound to a particular value in a [VMFrame]. 82 | class VMBoundVariable { 83 | /// The name of the variable. 84 | final String name; 85 | 86 | /// The value of the variable. 87 | /// 88 | /// If this variable is uninitialized, this will be 89 | /// [VMSentinel.notInitialized]. If it's currently being initialized, it will 90 | /// be [VMSentinel.beingInitialized]. If it's been optimized out, it will be 91 | /// [VMSentinel.optimizedOut]. Otherwise, it will be a [VMInstanceRef]. 92 | final value; 93 | 94 | VMBoundVariable._(Scope scope, Map json) 95 | : name = json["name"], 96 | value = newVMInstanceRefOrSentinel(scope, json["value"]); 97 | 98 | String toString() => "var $name = $value"; 99 | } 100 | -------------------------------------------------------------------------------- /lib/src/function.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:json_rpc_2/json_rpc_2.dart' as rpc; 8 | 9 | import 'breakpoint.dart'; 10 | import 'class.dart'; 11 | import 'code.dart'; 12 | import 'library.dart'; 13 | import 'object.dart'; 14 | import 'scope.dart'; 15 | import 'source_location.dart'; 16 | 17 | VMFunctionRef newVMFunctionRef(Scope scope, Map json) { 18 | if (json == null) return null; 19 | assert(json["type"] == "@Function" || json["type"] == "Function"); 20 | return new VMFunctionRef._(scope, json); 21 | } 22 | 23 | /// A reference to a Dart function or method. 24 | class VMFunctionRef implements VMObjectRef { 25 | final Scope _scope; 26 | 27 | /// The ID for this function, which is unique relative to its isolate. 28 | final String _id; 29 | 30 | /// Whether [_id] is guaranteed to be the same for different VM service 31 | /// function objects that refer to the same function. 32 | final bool _fixedId; 33 | 34 | Uri get observatoryUrl => _scope.observatoryUrlFor(_id); 35 | 36 | /// The name of this function. 37 | final String name; 38 | 39 | /// The owner of this function. 40 | /// 41 | /// This is either a [VMClassRef], a [VMLibraryRef], or another 42 | /// [VMFunctionRef]. 43 | final VMObjectRef owner; 44 | 45 | /// Whether this is a static function. 46 | final bool isStatic; 47 | 48 | /// Whether this is a const function—that is, a const constructor. 49 | final bool isConst; 50 | 51 | VMFunctionRef._(Scope scope, Map json) 52 | : _scope = scope, 53 | _id = json["id"], 54 | _fixedId = json["fixedId"] ?? false, 55 | name = json["name"], 56 | owner = _owner(scope, json["owner"]), 57 | isStatic = json["static"], 58 | isConst = json["const"]; 59 | 60 | /// Parses and returns the a library, class, or function. 61 | static VMObjectRef _owner(Scope scope, Map json) { 62 | if (json == null) return null; 63 | switch (json["type"]) { 64 | case "@Library": 65 | return newVMLibraryRef(scope, json); 66 | case "@Class": 67 | return newVMClassRef(scope, json); 68 | case "@Function": 69 | return newVMFunctionRef(scope, json); 70 | default: 71 | throw new StateError('Unknown owner type "${json["type"]}".'); 72 | } 73 | } 74 | 75 | Future load() async => 76 | new VMFunction._(_scope, await _scope.loadObject(_id)); 77 | 78 | /// Adds a breakpoint at the entrypoint of this function. 79 | /// 80 | /// If no breakpoint is possible for this function, this will throw an 81 | /// [rpc.RpcException] with code `102`. 82 | Future addBreakpoint() async { 83 | try { 84 | var response = 85 | await _scope.sendRequest("addBreakpointAtEntry", {"functionId": _id}); 86 | return newVMBreakpoint(_scope, response); 87 | } on rpc.RpcException catch (error) { 88 | // Error 102 indicates that the breakpoint couldn't be created. 89 | if (error.code == 102) return null; 90 | rethrow; 91 | } 92 | } 93 | 94 | bool operator ==(other) => 95 | other is VMFunctionRef && (_fixedId ? _id == other._id : super == other); 96 | 97 | int get hashCode => _fixedId ? _id.hashCode : super.hashCode; 98 | 99 | String toString() { 100 | var buffer = new StringBuffer(); 101 | if (isStatic && owner is VMClassRef) buffer.write("static "); 102 | if (isConst) buffer.write("const "); 103 | buffer.write(name); 104 | return buffer.toString(); 105 | } 106 | } 107 | 108 | /// A Dart function or method. 109 | class VMFunction extends VMFunctionRef implements VMObject { 110 | final int size; 111 | 112 | final VMClassRef klass; 113 | 114 | /// The location of this function in the source code. 115 | final VMSourceLocation location; 116 | 117 | /// The function's code. 118 | final VMCodeRef code; 119 | 120 | VMFunction._(Scope scope, Map json) 121 | : size = json["size"], 122 | klass = newVMClassRef(scope, json["class"]), 123 | location = newVMSourceLocation(scope, json["location"]), 124 | code = newVMCodeRef(scope, json["code"]), 125 | super._(scope, json); 126 | } 127 | -------------------------------------------------------------------------------- /lib/src/isolate.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:collection'; 7 | import 'dart:convert'; 8 | 9 | import 'package:async/async.dart'; 10 | import 'package:collection/collection.dart'; 11 | import 'package:json_rpc_2/json_rpc_2.dart' as rpc; 12 | 13 | import 'breakpoint.dart'; 14 | import 'error.dart'; 15 | import 'exceptions.dart'; 16 | import 'library.dart'; 17 | import 'pause_event.dart'; 18 | import 'scope.dart'; 19 | import 'sentinel.dart'; 20 | import 'source_report.dart'; 21 | import 'stack.dart'; 22 | import 'stream_manager.dart'; 23 | import 'utils.dart'; 24 | 25 | VMIsolateRef newVMIsolateRef(rpc.Peer peer, StreamManager streams, Map json) { 26 | if (json == null) return null; 27 | assert(json["type"] == "@Isolate" || json["type"] == "Isolate"); 28 | var scope = new Scope(peer, streams, json["id"]); 29 | return new VMIsolateRef._(scope, json); 30 | } 31 | 32 | /// A reference to an isolate on the remote VM. 33 | /// 34 | /// The full isolate with additional metadata can be loaded using [load]. 35 | class VMIsolateRef { 36 | final Scope _scope; 37 | 38 | /// A unique numeric ID for this isolate. 39 | /// 40 | /// Note that this may be larger than can be represented in Dart 41 | /// implementations that compile to JS; it's generally safer to use 42 | /// [numberAsString] instead. 43 | final int number; 44 | 45 | /// The string representation of [number]. 46 | final String numberAsString; 47 | 48 | /// A name identifying this isolate for debugging. 49 | /// 50 | /// This isn't guaranteed to be unique. It can be set using [setName]. 51 | final String name; 52 | 53 | /// A *relative* URL for humans to inspect and interact with this isolate in 54 | /// the Observatory UI. 55 | /// 56 | /// Because the VM service client doesn't always know the full location of the 57 | /// Observatory UI, this needs to be resolved against the absolute URL of the 58 | /// Observatory UI in order to be usable. 59 | Uri get observatoryUrl => Uri.parse( 60 | "#/inspect?isolateId=${Uri.encodeQueryComponent(_scope.isolateId)}"); 61 | 62 | /// A broadcast stream that emits a `null` value every time a garbage 63 | /// collection occurs in this isolate. 64 | Stream get onGC => _onGC; 65 | Stream _onGC; 66 | 67 | /// A broadcast stream that emits a new reference to this isolate every time 68 | /// its metadata changes. 69 | Stream get onUpdate => _onUpdate; 70 | Stream _onUpdate; 71 | 72 | /// A broadcast stream that emits a [VMPauseEvent] whenever this isolate is 73 | /// paused or resumed. 74 | Stream get onPauseOrResume => _onPauseOrResume; 75 | Stream _onPauseOrResume; 76 | 77 | /// A broadcast stream that emits a [VMBreakpoint] whenever a breakpoint is 78 | /// added. 79 | Stream get onBreakpointAdded => _onBreakpointAdded; 80 | Stream _onBreakpointAdded; 81 | 82 | /// A broadcast stream that emits custom events posted using `postEvent` from 83 | /// `dart:developer`. 84 | /// 85 | /// This is supported as of VM service version 3.1, or Dart SDK version 1.14. 86 | Stream get onExtensionEvent => _onExtensionEvent; 87 | Stream _onExtensionEvent; 88 | 89 | /// A broadcast stream that emits this isolate's standard output. 90 | /// 91 | /// This is only usable for embedders that provide access to `dart:io`. 92 | /// 93 | /// Note that as of the VM service version 3.0, this stream doesn't emit 94 | /// strings passed to the `print()` function unless the host process's 95 | /// standard output is actively being drained (see [sdk#24351][]). 96 | /// 97 | /// [sdk#24351]: https://github.com/dart-lang/sdk/issues/24351 98 | Stream> get stdout => _stdout; 99 | Stream> _stdout; 100 | 101 | /// A broadcast stream that emits this isolate's standard error. 102 | /// 103 | /// This is only usable for embedders that provide access to `dart:io`. 104 | Stream> get stderr => _stderr; 105 | Stream> _stderr; 106 | 107 | /// A broadcast stream that emits an event whenever a new VM service extension 108 | /// RPC is registered. 109 | /// 110 | /// Each event is the name of a registered RPC. 111 | /// 112 | /// This is supported as of VM service version 3.1, or Dart SDK version 1.14. 113 | Stream get onExtensionAdded => _onExtensionAdded; 114 | Stream _onExtensionAdded; 115 | 116 | /// A future that fires when the isolate exits. 117 | /// 118 | /// If the isolate has already exited, this will complete immediately. 119 | Future get onExit => _onExitMemo.runOnce(() async { 120 | try { 121 | await _scope.getInState(_scope.streams.isolate, () async { 122 | try { 123 | await load(); 124 | return null; 125 | } on VMSentinelException catch (_) { 126 | // Return a non-null value to indicate that the breakpoint is in the 127 | // expected state—that is, it no longer exists. 128 | return true; 129 | } 130 | }, (json) { 131 | if (json["isolate"]["id"] != _scope.isolateId) return null; 132 | if (json["kind"] != "IsolateExit") return null; 133 | return true; 134 | }); 135 | } on StateError catch (_) { 136 | // Ignore state errors. They indicate that the underlying stream closed 137 | // before an exit event was fired, which means that the process and thus 138 | // this isolate is dead. 139 | } 140 | }); 141 | final _onExitMemo = new AsyncMemoizer(); 142 | 143 | VMIsolateRef._(this._scope, Map json) 144 | : number = int.parse(json["number"]), 145 | numberAsString = json["number"], 146 | name = json["name"] { 147 | _onGC = _transform(_scope.streams.gc, (json, sink) { 148 | if (json["kind"] == "GC") sink.add(null); 149 | }); 150 | 151 | _onUpdate = _transform(_scope.streams.isolate, (json, sink) { 152 | if (json["kind"] != "IsolateUpdate") return; 153 | sink.add(new VMIsolateRef._(_scope, json["isolate"])); 154 | }); 155 | 156 | _onExtensionAdded = _transform(_scope.streams.isolate, (json, sink) { 157 | if (json["kind"] != "ServiceExtensionAdded") return; 158 | sink.add(json["extensionRPC"]); 159 | }); 160 | 161 | _onPauseOrResume = _transform(_scope.streams.debug, (json, sink) { 162 | var event = newVMPauseEvent(_scope, json); 163 | if (event != null) sink.add(event); 164 | }); 165 | 166 | _onBreakpointAdded = _transform(_scope.streams.debug, (json, sink) { 167 | if (json["kind"] != "BreakpointAdded") return; 168 | sink.add(newVMBreakpoint(_scope, json["breakpoint"])); 169 | }); 170 | 171 | _stdout = _transform(_scope.streams.stdout, (json, sink) { 172 | if (json["kind"] != "WriteEvent") return; 173 | var bytes = base64Decode(json["bytes"]); 174 | sink.add(bytes); 175 | }); 176 | 177 | _stderr = _transform(_scope.streams.stderr, (json, sink) { 178 | if (json["kind"] != "WriteEvent") return; 179 | sink.add(base64Decode(json["bytes"])); 180 | }); 181 | 182 | _onExtensionEvent = _transform(_scope.streams.extension, (json, sink) { 183 | sink.add(new VMExtensionEvent._(json)); 184 | }); 185 | } 186 | 187 | /// Like [transform], but only calls [handleData] for events related to this 188 | /// isolate. 189 | Stream _transform( 190 | Stream stream, void handleData(Map json, Sink sink)) { 191 | return transform(stream, (json, sink) { 192 | if (json["isolate"]["id"] != _scope.isolateId) return; 193 | handleData(json, sink); 194 | }); 195 | } 196 | 197 | /// Loads the full representation of this isolate once it becomes runnable. 198 | /// 199 | /// This will work whether this isolate is already runnable or has yet to 200 | /// become runnable. 201 | /// 202 | /// This is only supported on the VM service protocol version 3.0 and greater. 203 | Future loadRunnable() { 204 | return _scope.getInState(_scope.streams.isolate, () async { 205 | var isolate = await load(); 206 | return isolate is VMRunnableIsolate ? isolate : null; 207 | }, (json) async { 208 | if (json["kind"] != "IsolateRunnable") return null; 209 | return (await load()) as VMRunnableIsolate; 210 | }); 211 | } 212 | 213 | // Note that if anyone else is using the VM service, the VM may be unpaused by 214 | // the time this fires. 215 | 216 | /// Returns a future that completes once this isolate is paused. 217 | /// 218 | /// This works whether the isolate is already paused or has yet to be paused. 219 | /// Note that if any other code (other VM service clients or other isolates) 220 | /// unpauses the isolate, it may be unpaused by the time the returned future 221 | /// fires. 222 | Future waitUntilPaused() { 223 | return _scope.getInState(_scope.streams.debug, () async { 224 | return (await load()).isPaused; 225 | }, (json) { 226 | return json["kind"] == "PauseStart" || 227 | json["kind"] == "PauseException" || 228 | json["kind"] == "PauseExit" || 229 | json["kind"] == "PauseInterrupted" || 230 | json["kind"] == "PauseBreakpoint"; 231 | }); 232 | } 233 | 234 | /// Loads the full representation of this isolate. 235 | /// 236 | /// Throws a [VMSentinelException] if this isolate is no longer available. 237 | Future load() async { 238 | var response = await _scope.sendRequest("getIsolate"); 239 | 240 | // Work around sdk#24142. 241 | if (response["type"] == "Error") { 242 | throw new VMSentinelException(VMSentinel.collected); 243 | } else if (response["type"] == "Sentinel") { 244 | throw new VMSentinelException(newVMSentinel(response)); 245 | } else { 246 | return response["rootLib"] == null || 247 | // Work around sdk#24140 248 | response["rootLib"]["type"] == "@Instance" 249 | ? new VMIsolate._(_scope, response) 250 | : new VMRunnableIsolate._(_scope, response); 251 | } 252 | } 253 | 254 | /// Returns a broadcast stream that emits custom events posted via `postEvent` 255 | /// from the `dart:developer` package. 256 | /// 257 | /// Unlike [onExtensionEvent], this only emits events for which 258 | /// [VMExtensionEvent.kind] is [kind]. If [prefix] is `true`, it also emits 259 | /// events for which [VMExtensionEvent.kind] starts with [kind]. 260 | /// 261 | /// This is supported as of VM service version 3.1, or Dart SDK version 1.14. 262 | Stream selectExtensionEvents(String kind, 263 | {bool prefix: false}) { 264 | return transform(_onExtensionEvent, (event, sink) { 265 | if (prefix == null ? event.kind == kind : event.kind.startsWith(kind)) { 266 | sink.add(event); 267 | } 268 | }); 269 | } 270 | 271 | /// Returns the isolate's current execution stack and message queue. 272 | Future getStack() async => 273 | newVMStack(_scope, await _scope.sendRequest("getStack")); 274 | 275 | /// Pauses this isolate. 276 | /// 277 | /// The returned future may complete before the isolate is paused. 278 | Future pause() async { 279 | await _scope.sendRequest("pause"); 280 | } 281 | 282 | /// Resumes execution of this isolate, if it's paused. 283 | /// 284 | /// [step] controls how execution proceeds; it defaults to [VMStep.resume]. 285 | /// 286 | /// Throws an [rpc.RpcException] if the isolate isn't paused. 287 | Future resume({VMStep step}) { 288 | if (step == null) step = VMStep.resume; 289 | return _scope.sendRequest( 290 | "resume", step == VMStep.resume ? {} : {"step": step._value}); 291 | } 292 | 293 | /// Sets the pause behaviour for exceptions. 294 | Future setExceptionPauseMode(VMExceptionPauseMode mode) => 295 | _scope.sendRequest("setExceptionPauseMode", {"mode": mode._value}); 296 | 297 | /// Sets the [name] of the isolate. 298 | /// 299 | /// Note that since this object is immutable, it needs to be reloaded to see 300 | /// the new name. 301 | Future setName(String name) => _scope.sendRequest("setName", {"name": name}); 302 | 303 | /// Adds a breakpoint at [line] (and optionally [column]) in the script with 304 | /// the given canonical [uri]. 305 | /// 306 | /// Both [line] and [column] are 1-based. The [uri] may be a [String] or a 307 | /// [Uri]. 308 | Future addBreakpoint(uri, int line, {int column}) async { 309 | if (uri is! String && uri is! Uri) { 310 | throw new ArgumentError("Invalid uri '$uri', must be a Uri or a String."); 311 | } 312 | 313 | var params = {"scriptUri": uri.toString(), "line": line}; 314 | if (column != null) params["column"] = column; 315 | 316 | try { 317 | var response = 318 | await _scope.sendRequest("addBreakpointWithScriptUri", params); 319 | return newVMBreakpoint(_scope, response); 320 | } on rpc.RpcException catch (error) { 321 | // Error 102 indicates that the breakpoint couldn't be created. 322 | if (error.code == 102) return null; 323 | rethrow; 324 | } 325 | } 326 | 327 | /// Returns a future that completes once the VM service extension RPC with the 328 | /// given [name] is available. 329 | /// 330 | /// This works whether the extension is already registered or has yet to be 331 | /// registered. 332 | /// 333 | /// This is supported as of VM service version 3.1, or Dart SDK version 1.14. 334 | Future waitForExtension(String name) async { 335 | return _scope.getInState(_scope.streams.isolate, () async { 336 | var extensions = (await load()).extensionRpcs; 337 | return extensions.contains(name); 338 | }, (Map json) { 339 | return json["kind"] == "ServiceExtensionAdded" && 340 | json["extensionRPC"] == name; 341 | }).then((_) => null); 342 | } 343 | 344 | /// Invokes the VM service extension RPC named [method] registered in this 345 | /// isolate. 346 | /// 347 | /// VM service extensions are registered using the `registerExtension` method 348 | /// in `dart:developer`. The [method] name must be the same as the name passed 349 | /// to `registerExtension`, which means it must begin with "ext.". 350 | /// 351 | /// The [params] are passed to the extension handler. 352 | /// 353 | /// Returns the extension handler's decoded response. 354 | /// 355 | /// This is supported as of VM service version 3.1, or Dart SDK version 1.14. 356 | Future invokeExtension(String method, [Map params]) { 357 | if (!method.startsWith('ext.')) { 358 | throw new ArgumentError.value( 359 | method, 'method', 'must begin with "ext." prefix'); 360 | } 361 | return _scope.sendRequestRaw(method, params); 362 | } 363 | 364 | /// Generates a report of code coverage information and possible break points 365 | /// for the scripts in this isolate. 366 | /// 367 | /// If [includeCoverageReport] is `true`, the report includes code coverage 368 | /// information via [VMSourceReportRange.hits] and 369 | /// [VMSourceReportRange.misses] in [VMSourceReport.ranges]. Otherwise, 370 | /// these properties are `null`. 371 | /// 372 | /// If [includePossibleBreakpoints] is `true`, the report includes a list of 373 | /// token positions which correspond to possible breakpoints via 374 | /// [VMSourceReportRange.possibleBreakpoints] in [VMSourceReport.ranges]. 375 | /// Otherwise, [VMSourceReportRange.possibleBreakpoints] is `null`. 376 | /// 377 | /// If [forceCompile] is `true`, all functions in the range of the report 378 | /// will be compiled. If `false`, functions that are never used may not appear 379 | /// in [VMSourceReportRange.misses]. Forcing compilation can cause a 380 | /// compilation error, which could terminate the running Dart program. 381 | Future getSourceReport( 382 | {bool includeCoverageReport: true, 383 | bool includePossibleBreakpoints: true, 384 | bool forceCompile: false}) async { 385 | var reports = []; 386 | if (includeCoverageReport) reports.add('Coverage'); 387 | if (includePossibleBreakpoints) reports.add('PossibleBreakpoints'); 388 | 389 | var params = {'reports': reports}; 390 | if (forceCompile) params['forceCompile'] = true; 391 | 392 | var json = await _scope.sendRequest('getSourceReport', params); 393 | return newSourceReport(_scope, json); 394 | } 395 | 396 | bool operator ==(other) => 397 | other is VMIsolateRef && other._scope.isolateId == _scope.isolateId; 398 | 399 | int get hashCode => _scope.isolateId.hashCode; 400 | 401 | String toString() => name; 402 | } 403 | 404 | /// A full isolate on the remote VM. 405 | class VMIsolate extends VMIsolateRef { 406 | /// The current pause on exception mode for this isolate. 407 | final VMExceptionPauseMode exceptionPauseMode; 408 | 409 | /// The time that the isolate started running. 410 | final DateTime startTime; 411 | 412 | /// The number of live ports on this isolate. 413 | final int livePorts; 414 | 415 | /// Whether this isolate will pause before it exits. 416 | final bool pauseOnExit; 417 | 418 | /// The last pause event delivered to this isolate. 419 | /// 420 | /// If the isolate is running, this will be a [VMResumeEvent]. 421 | /// 422 | /// As of VM service version 3.4, this will be a [VMNoneEvent] before the 423 | /// isolate is runnable. 424 | final VMPauseEvent pauseEvent; 425 | 426 | /// Whether this isolate is paused. 427 | bool get isPaused => 428 | pauseEvent is! VMNoneEvent && pauseEvent is! VMResumeEvent; 429 | 430 | /// The error that's causing the isolate to exit or `null`. 431 | final VMError error; 432 | 433 | /// All breakpoints currently registered for this isolate. 434 | final List breakpoints; 435 | 436 | /// The VM service extension RPCs that are currently registered for this 437 | /// isolate. 438 | /// 439 | /// This is supported as of VM service version 3.1, or Dart SDK version 1.14. 440 | final List extensionRpcs; 441 | 442 | VMIsolate._(Scope scope, Map json) 443 | : exceptionPauseMode = 444 | new VMExceptionPauseMode._(json["exceptionPauseMode"]), 445 | startTime = new DateTime.fromMillisecondsSinceEpoch( 446 | // Prior to v3.0, this was emitted as a double rather than an int. 447 | json["startTime"].round()), 448 | livePorts = json["livePorts"], 449 | pauseOnExit = json["pauseOnExit"], 450 | pauseEvent = newVMPauseEvent(scope, json["pauseEvent"]), 451 | error = newVMError(scope, json["error"]), 452 | breakpoints = new List.unmodifiable(json["breakpoints"] 453 | .map((breakpoint) => newVMBreakpoint(scope, breakpoint))), 454 | extensionRpcs = new UnmodifiableListView( 455 | List.castFrom(json["extensionRPCs"] ?? [])), 456 | super._(scope, json); 457 | } 458 | 459 | /// A full isolate on the remote VM that's ready to run code. 460 | /// 461 | /// The VM service exposes isolates very early, before their contents are 462 | /// fully-loaded. These in-progress isolates, represented by plain [VMIsolate] 463 | /// instances, have limited amounts of metadata available. Only once they're 464 | /// runnable is the full suite of metadata available. 465 | /// 466 | /// A [VMRunnableIsolate] can always be retrieved using 467 | /// [VMIsolateRef.loadRunnable]. In addition, one will be returned by 468 | /// [VMIsolate.load] if the remote isolate is runnable. 469 | class VMRunnableIsolate extends VMIsolate { 470 | /// The root library for this isolate. 471 | final VMLibraryRef rootLibrary; 472 | 473 | /// All the libraries (transitively) loaded in this isolate, indexed by their 474 | /// canonical URIs. 475 | final Map libraries; 476 | 477 | VMRunnableIsolate._(Scope scope, Map json) 478 | : rootLibrary = newVMLibraryRef(scope, json["rootLib"]), 479 | libraries = new UnmodifiableMapView(new Map.fromIterable( 480 | json["libraries"], 481 | key: (library) => Uri.parse(library["uri"]), 482 | value: (library) => newVMLibraryRef(scope, library))), 483 | super._(scope, json); 484 | 485 | Future loadRunnable() => load(); 486 | 487 | Future load() => 488 | super.load().then((v) => v as VMRunnableIsolate); 489 | 490 | String toString() => "Isolate running $rootLibrary"; 491 | } 492 | 493 | /// An enum of ways to resume an isolate's execution using 494 | /// [VMIsolateRef.resume]. 495 | class VMStep { 496 | /// The isolate resumes regular execution. 497 | static const resume = const VMStep._("Resume"); 498 | 499 | /// The isolate takes a single step into a function call. 500 | static const into = const VMStep._("Into"); 501 | 502 | /// The isolate takes a single step, skipping over function calls. 503 | static const over = const VMStep._("Over"); 504 | 505 | /// The isolate continues until the execution returns from the an await, yield, 506 | /// or yield* statement. 507 | /// 508 | /// Note that this value is only valid from VM service version 3.3 and when 509 | /// [VMPauseEvent.atAsyncSuspension] is true. 510 | static const overAsyncSuspension = const VMStep._("OverAsyncSuspension"); 511 | 512 | /// The isolate continues until it exits the current function. 513 | static const out = const VMStep._("Out"); 514 | 515 | /// The string name of the step type. 516 | final String _value; 517 | 518 | const VMStep._(this._value); 519 | 520 | String toString() => _value; 521 | } 522 | 523 | /// An enum of exception pause behaviour for use in [VMIsolateRef.setExceptionPauseMode]. 524 | class VMExceptionPauseMode { 525 | /// The isolate will not pause on any exceptions. 526 | static const none = const VMExceptionPauseMode._("None"); 527 | 528 | /// The isolate will pause on any unhandled exceptions. 529 | static const unhandled = const VMExceptionPauseMode._("Unhandled"); 530 | 531 | /// The isolate will pause on all exceptions. 532 | static const all = const VMExceptionPauseMode._("All"); 533 | 534 | /// The string name of the exception pause mode. 535 | final String _value; 536 | 537 | const VMExceptionPauseMode._(this._value); 538 | 539 | String toString() => _value; 540 | } 541 | 542 | /// An event posted via `postEvent` from the `dart:developer` package. 543 | class VMExtensionEvent { 544 | /// The kind, which identifies the type of event and its source. 545 | final String kind; 546 | 547 | /// The event's payload. 548 | final Map data; 549 | 550 | VMExtensionEvent._(Map json) 551 | : kind = json['extensionKind'], 552 | data = json['extensionData']; 553 | } 554 | -------------------------------------------------------------------------------- /lib/src/library.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:collection'; 7 | 8 | import 'class.dart'; 9 | import 'field.dart'; 10 | import 'function.dart'; 11 | import 'instance.dart'; 12 | import 'object.dart'; 13 | import 'scope.dart'; 14 | import 'script.dart'; 15 | 16 | VMLibraryRef newVMLibraryRef(Scope scope, Map json) { 17 | if (json == null) return null; 18 | assert(json["type"] == "@Library" || json["type"] == "Library"); 19 | return new VMLibraryRef._(scope, json); 20 | } 21 | 22 | /// A reference to a Dart library—that is, a single importable Dart file. 23 | class VMLibraryRef implements VMObjectRef { 24 | final Scope _scope; 25 | 26 | /// The ID for this library, which is unique relative to its isolate. 27 | final String _id; 28 | 29 | /// Whether [_id] is guaranteed to be the same for different VM service 30 | /// library objects that refer to the same library. 31 | final bool _fixedId; 32 | 33 | Uri get observatoryUrl => _scope.observatoryUrlFor(_id); 34 | 35 | /// The name of this library, derived from its library tag. 36 | final String name; 37 | 38 | /// The canonical URI for this library. 39 | final Uri uri; 40 | 41 | VMLibraryRef._(this._scope, Map json) 42 | : _id = json["id"], 43 | _fixedId = json["fixedId"] ?? false, 44 | name = json["name"], 45 | uri = Uri.parse(json["uri"]); 46 | 47 | Future load() async => 48 | new VMLibrary._(_scope, await _scope.loadObject(_id)); 49 | 50 | /// Enables breakpoints and stepping for this library. 51 | Future setDebuggable() async { 52 | await _scope.sendRequest( 53 | "setLibraryDebuggable", {"libraryId": _id, "isDebuggable": true}); 54 | } 55 | 56 | /// Disables breakpoints and stepping for this library. 57 | Future setNotDebuggable() async { 58 | await _scope.sendRequest( 59 | "setLibraryDebuggable", {"libraryId": _id, "isDebuggable": false}); 60 | } 61 | 62 | /// Evaluates [expression] in the context of this library. 63 | /// 64 | /// Throws a [VMErrorException] if evaluating the expression throws an error. 65 | Future evaluate(String expression) => 66 | _scope.evaluate(_id, expression); 67 | 68 | bool operator ==(other) => 69 | other is VMLibraryRef && (_fixedId ? _id == other._id : super == other); 70 | 71 | int get hashCode => _fixedId ? _id.hashCode : super.hashCode; 72 | 73 | String toString() => uri.toString(); 74 | } 75 | 76 | /// A a Dart library—that is, a single importable Dart file. 77 | class VMLibrary extends VMLibraryRef implements VMObject { 78 | final VMClassRef klass; 79 | 80 | final int size; 81 | 82 | /// Whether breakpoints and stepping are enabled for this library. 83 | final bool isDebuggable; 84 | 85 | /// The imports and exports for this library. 86 | final List dependencies; 87 | 88 | /// The scripts that make up the library. 89 | /// 90 | /// Most libraries have only one script, but the `part` directive means that 91 | /// it can have multiple. 92 | final List scripts; 93 | 94 | /// The fields defined in this library, indexed by name. 95 | final Map fields; 96 | 97 | /// The top-level functions defined in this library, indexed by name. 98 | final Map functions; 99 | 100 | /// The classes defined in this library, indexed by name. 101 | final Map classes; 102 | 103 | VMLibrary._(Scope scope, Map json) 104 | : klass = newVMClassRef(scope, json["class"]), 105 | size = json["size"], 106 | isDebuggable = json["debuggable"], 107 | dependencies = new List.unmodifiable(json["dependencies"] 108 | .map((dependency) => new VMLibraryDependency._(scope, dependency))), 109 | scripts = new List.unmodifiable( 110 | json["scripts"].map((script) => newVMScriptRef(scope, script))), 111 | fields = new UnmodifiableMapView(new Map.fromIterable(json["variables"], 112 | key: (field) => field["name"], 113 | value: (field) => newVMFieldRef(scope, field))), 114 | functions = new UnmodifiableMapView(new Map.fromIterable( 115 | json["functions"], 116 | key: (function) => function["name"], 117 | value: (function) => newVMFunctionRef(scope, function))), 118 | classes = new UnmodifiableMapView(new Map.fromIterable(json["classes"], 119 | key: (klass) => klass["name"], 120 | value: (klass) => newVMClassRef(scope, klass))), 121 | super._(scope, json); 122 | } 123 | 124 | /// A single dependency—that is, an import or an export. 125 | class VMLibraryDependency { 126 | /// Whether this is an import. 127 | /// 128 | /// This is always the opposite of [isExport]. 129 | final bool isImport; 130 | 131 | /// Whether this is an export. 132 | /// 133 | /// This is always the opposite of [isImport]. 134 | bool get isExport => !isImport; 135 | 136 | /// Whether this is a deferred import. 137 | final bool isDeferred; 138 | 139 | /// The prefix for this import, or `null` if it's unprefixed or an export. 140 | final String prefix; 141 | 142 | /// The library that's imported or exported. 143 | final VMLibraryRef target; 144 | 145 | VMLibraryDependency._(Scope scope, Map json) 146 | : isImport = json["isImport"], 147 | isDeferred = json["isDeferred"], 148 | prefix = json["prefix"], 149 | target = new VMLibraryRef._(scope, json["target"]); 150 | 151 | String toString() { 152 | var buffer = new StringBuffer(isImport ? "import" : "export"); 153 | buffer.write(' "${target.uri}"'); 154 | if (isDeferred) buffer.write(" deferred"); 155 | if (prefix != null) buffer.write(" as $prefix"); 156 | return buffer.toString(); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /lib/src/message.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'function.dart'; 8 | import 'instance.dart'; 9 | import 'scope.dart'; 10 | import 'source_location.dart'; 11 | 12 | VMMessage newVMMessage(Scope scope, Map json) { 13 | if (json == null) return null; 14 | assert(json["type"] == "Message"); 15 | return new VMMessage._(scope, json); 16 | } 17 | 18 | /// A pending message to the current isolate. 19 | class VMMessage { 20 | final Scope _scope; 21 | 22 | /// The ID of the message's payload. 23 | final String _objectId; 24 | 25 | /// The index in the isolate's message queue. 26 | /// 27 | /// The message with the index of 0 will be processed next. 28 | final int index; 29 | 30 | /// An advisory name describing the message. 31 | final String name; 32 | 33 | final int size; 34 | 35 | /// The function that will be called to handle this message. 36 | final VMFunctionRef handler; 37 | 38 | /// The source location of the handler function. 39 | /// 40 | /// This may be `null` if there is no handler. 41 | final VMSourceLocation location; 42 | 43 | VMMessage._(Scope scope, Map json) 44 | : _scope = scope, 45 | _objectId = json["messageObjectId"], 46 | index = json["index"], 47 | name = json["name"], 48 | size = json["size"], 49 | handler = newVMFunctionRef(scope, json["handler"]), 50 | location = newVMSourceLocation(scope, json["location"]); 51 | 52 | /// Loads the message's payload. 53 | /// 54 | /// This will throw a [VMSentinelException] if the value has expired. 55 | Future loadValue() async => 56 | newVMInstance(_scope, await _scope.loadObject(_objectId)); 57 | 58 | String toString() => name; 59 | } 60 | -------------------------------------------------------------------------------- /lib/src/object.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'class.dart'; 8 | 9 | /// A reference to persistent service-protocol object that's local to an 10 | /// isolate. 11 | /// 12 | /// Protocol objects include but are not limited to instances of Dart classes. 13 | /// Equality for an object is based on the identity of the remote object where 14 | /// that's available. Otherwise, it's based on local Dart object identity. 15 | /// 16 | /// Most objects will have only a subset of their information available as a 17 | /// reference, and must be [load]ed to retrieve their full information. 18 | abstract class VMObjectRef { 19 | /// A *relative* URL for humans to inspect and possibly interact with this 20 | /// object in the Observatory UI. 21 | /// 22 | /// Because the VM service client doesn't always know the full location of the 23 | /// Observatory UI, this needs to be resolved against the absolute URL of the 24 | /// Observatory UI in order to be usable. 25 | /// 26 | /// Note that the Observatory may not have useful UI components for all 27 | /// possible object types. 28 | Uri get observatoryUrl; 29 | 30 | /// Loads a full version of this object. 31 | /// 32 | /// This will throw a [VMSentinelException] if this object is no longer 33 | /// available. This will contain [VMSentinel.expired] if this object was 34 | /// temporary and has expired, and [VMSentinel.collected] if this was a heap 35 | /// object that has been collected or a non-heap object that has been 36 | /// deleted. 37 | Future load(); 38 | } 39 | 40 | /// A persistent service-protocol object that's local to an isolate. 41 | abstract class VMObject implements VMObjectRef { 42 | /// This object's class, or `null` if this object wasn't allocated on the Dart 43 | /// heap. 44 | /// 45 | /// Objects that aren't [VMInstance]s will have internal VM classes. 46 | /// 47 | /// Moving an object onto or off of the heap is considered an implementation 48 | /// detail for objects other than instances. 49 | VMClassRef get klass; 50 | 51 | /// The size (in bytes) of this object on the heap. 52 | /// 53 | /// If an object is not heap-allocated, this will be `null`. 54 | /// 55 | /// Note that the size can be zero for some objects. In the current VM 56 | /// implementation, this occurs for small integers, which are stored entirely 57 | /// within their object pointers. 58 | int get size; 59 | } 60 | -------------------------------------------------------------------------------- /lib/src/pause_event.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'breakpoint.dart'; 6 | import 'frame.dart'; 7 | import 'instance.dart'; 8 | import 'scope.dart'; 9 | 10 | VMPauseEvent newVMPauseEvent(Scope scope, Map json) { 11 | if (json == null) return null; 12 | 13 | assert(json["type"] == "Event"); 14 | switch (json["kind"]) { 15 | case "PauseStart": 16 | return new VMPauseStartEvent._(scope, json); 17 | case "PauseExit": 18 | return new VMPauseExitEvent._(scope, json); 19 | case "PauseBreakpoint": 20 | return new VMPauseBreakpointEvent._(scope, json); 21 | case "PauseInterrupted": 22 | return new VMPauseInterruptedEvent._(scope, json); 23 | case "PauseException": 24 | return new VMPauseExceptionEvent._(scope, json); 25 | case "Resume": 26 | return new VMResumeEvent._(scope, json); 27 | case "None": 28 | return new VMNoneEvent._(scope, json); 29 | default: 30 | return null; 31 | } 32 | } 33 | 34 | /// An event indicating that an isolate has been paused or resumed. 35 | abstract class VMPauseEvent { 36 | /// Whether the isolate is paused at an await, yield, or yield* statement 37 | /// 38 | /// This is only available from VM service version 3.3 and only provided for 39 | /// the event kinds: 40 | /// PauseBreakpoint 41 | /// PauseInterrupted 42 | final bool atAsyncSuspension; 43 | 44 | /// The top stack frame associated with this event. 45 | /// 46 | /// This is `null` for [VMPauseInterruptedEvent]s when the interrupt arrived 47 | /// while the Isolate was idle, and for pause events that don't occur while 48 | /// the isolate is running code. 49 | final VMFrame topFrame; 50 | 51 | /// The time at which the event fired. 52 | /// 53 | /// This is only available in version 3.0 or greater of the VM service 54 | /// protocol. 55 | final DateTime time; 56 | 57 | VMPauseEvent._(Scope scope, Map json) 58 | : atAsyncSuspension = json["atAsyncSuspension"], 59 | topFrame = newVMFrame(scope, json["frame"]), 60 | time = json["timestamp"] == null 61 | ? null 62 | : new DateTime.fromMillisecondsSinceEpoch(json["timestamp"]); 63 | } 64 | 65 | /// An event indicating that an isolate was paused as it started, before it 66 | /// executed any code. 67 | class VMPauseStartEvent extends VMPauseEvent { 68 | VMPauseStartEvent._(Scope scope, Map json) : super._(scope, json); 69 | 70 | String toString() => "pause before start"; 71 | } 72 | 73 | /// An event indicating that an isolate was paused as it exited, before it 74 | /// terminated. 75 | class VMPauseExitEvent extends VMPauseEvent { 76 | VMPauseExitEvent._(Scope scope, Map json) : super._(scope, json); 77 | 78 | String toString() => "pause before exit"; 79 | } 80 | 81 | /// An event indicating that an isolate was paused at a breakpoint or due to 82 | /// stepping through code. 83 | class VMPauseBreakpointEvent extends VMPauseEvent { 84 | /// The breakpoint that caused this pause event. 85 | final VMBreakpoint breakpoint; 86 | 87 | /// The list of breakpoints at the current execution point. 88 | final List breakpoints; 89 | 90 | VMPauseBreakpointEvent._(Scope scope, Map json) 91 | : breakpoint = newVMBreakpoint(scope, json["breakpoint"]), 92 | breakpoints = new List.unmodifiable(json["pauseBreakpoints"] 93 | .map((breakpoint) => newVMBreakpoint(scope, breakpoint))), 94 | super._(scope, json); 95 | 96 | String toString() => "pause at $breakpoint"; 97 | } 98 | 99 | /// An event indicating that an isolate was paused due to an interruption. 100 | /// 101 | /// This usually means its process received `SIGQUIT`. 102 | class VMPauseInterruptedEvent extends VMPauseEvent { 103 | VMPauseInterruptedEvent._(Scope scope, Map json) : super._(scope, json); 104 | 105 | String toString() => "pause on interrupt"; 106 | } 107 | 108 | /// An event indicating that an isolate was paused due to an exception. 109 | class VMPauseExceptionEvent extends VMPauseEvent { 110 | /// The exception that caused the isolate to become paused. 111 | final VMInstanceRef exception; 112 | 113 | VMPauseExceptionEvent._(Scope scope, Map json) 114 | : exception = newVMInstanceRef(scope, json["exception"]), 115 | super._(scope, json); 116 | 117 | String toString() => "pause on exception"; 118 | } 119 | 120 | /// An event indicating that an isolate was unpaused. 121 | class VMResumeEvent extends VMPauseEvent { 122 | VMResumeEvent._(Scope scope, Map json) : super._(scope, json); 123 | 124 | String toString() => "resume"; 125 | } 126 | 127 | /// An event indicating that an isolate was unpaused. 128 | class VMNoneEvent extends VMPauseEvent { 129 | VMNoneEvent._(Scope scope, Map json) : super._(scope, json); 130 | 131 | String toString() => "none"; 132 | } 133 | -------------------------------------------------------------------------------- /lib/src/scope.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:json_rpc_2/json_rpc_2.dart' as rpc; 8 | 9 | import 'error.dart'; 10 | import 'exceptions.dart'; 11 | import 'instance.dart'; 12 | import 'sentinel.dart'; 13 | import 'stream_manager.dart'; 14 | 15 | /// A class representing the state inherent in the scope of a single isolate and 16 | /// the values available therein. 17 | class Scope { 18 | /// The JSON-RPC 2.0 peer for communicating with the service protocol. 19 | final rpc.Peer peer; 20 | 21 | /// The streams shared among the entire service protocol client. 22 | final StreamManager streams; 23 | 24 | /// The ID of this scope's isolate. 25 | /// 26 | /// This is necessary for all isolate-scoped RPCs. 27 | final String isolateId; 28 | 29 | Scope(this.peer, this.streams, this.isolateId); 30 | 31 | /// Returns the [VMObjectRef.observatoryUrl] for an object with the given 32 | /// [id]. 33 | Uri observatoryUrlFor(String id) => 34 | Uri.parse("#/inspect?isolateId=${Uri.encodeQueryComponent(isolateId)}&" 35 | "objectId=${Uri.encodeQueryComponent(id)}"); 36 | 37 | /// Given the ID for a [VMObjectRef], loads the JSON for its corresponding 38 | /// [VMObject]. 39 | /// 40 | /// If the `getObject` RPC returns a sentinel, this throws a 41 | /// [VMSentinelException]. 42 | Future loadObject(String id) async { 43 | var result = await sendRequest("getObject", {"objectId": id}); 44 | 45 | if (result["type"] == "Sentinel") { 46 | throw new VMSentinelException(newVMSentinel(result)); 47 | } 48 | 49 | return result; 50 | } 51 | 52 | /// Calls an isolate-scoped RPC named [method] with [params]. 53 | /// 54 | /// This always adds the `isolateId` parameter to the RPC. 55 | Future> sendRequest(String method, 56 | [Map params]) async => 57 | (await sendRequestRaw(method, params)) as Map; 58 | 59 | /// Like [sendRequest], but doesn't assert that the result is a Map. 60 | Future sendRequestRaw(String method, 61 | [Map params]) async { 62 | var allParams = {"isolateId": isolateId} 63 | ..addAll(params ?? {}); 64 | return await peer.sendRequest(method, allParams); 65 | } 66 | 67 | /// Evaluates [expression] in the context of the object identified by [id]. 68 | /// 69 | /// Throws a [VMErrorException] if evaluating the expression throws an error. 70 | /// Throws a [VMSentinelException] if the object has expired. 71 | Future evaluate(String id, String expression) async { 72 | var result = await sendRequest( 73 | "evaluate", {"targetId": id, "expression": expression}); 74 | 75 | switch (result["type"]) { 76 | case "Sentinel": 77 | throw new VMSentinelException(newVMSentinel(result)); 78 | case "@Error": 79 | throw new VMErrorException(newVMErrorRef(this, result)); 80 | case "@Instance": 81 | return newVMInstanceRef(this, result); 82 | default: 83 | throw new StateError('Unexpected Object type "${result["type"]}".'); 84 | } 85 | } 86 | 87 | /// A race-condition-free way of getting an object in a particular state. 88 | /// 89 | /// This listens to a [stream] of events describing the state of an object at 90 | /// the same time as it checks if the current state of the object in the VM is 91 | /// acceptable (for example, if an Isolate is runnable). This ensures that if 92 | /// the object is already in the desired state, we know about it rather than 93 | /// waiting for an event that will never come. 94 | /// 95 | /// First, [immediate] is called to do an asynchronous load of the object to 96 | /// check its current state. If it's in the proper state, the returned Future 97 | /// should complete to that value; otherwise, it should complete to `null` or 98 | /// `false`. 99 | /// 100 | /// Meanwhile, [onEvent] is called with every event from [stream] that matches 101 | /// this scope's isolate. It should *synchronously* return `null` or `false` 102 | /// for irrelevant events; once it sees a relevant event, it should return a 103 | /// non-`null`, non-`false` value (which may be a Future). 104 | /// 105 | /// This returns the value returned by [immediate] if it's not `null` or 106 | /// `false`, or else the first non-`null`, non-`false` value returned by 107 | /// [onEvent]. 108 | Future getInState( 109 | Stream stream, Future immediate(), onEvent(Map json)) async { 110 | var completer = new Completer.sync(); 111 | 112 | // Don't top-level errors from the completer. These may come in from the 113 | // stream subscription while we're still waiting for `load()` to complete. 114 | completer.future.catchError((_) {}); 115 | 116 | // To avoid a race condition, we need to make sure we're listening to the 117 | // event stream before we load the current state, so that if the object 118 | // reaches the desired state during the load we'll see it. 119 | var subscription; 120 | subscription = stream.listen((json) { 121 | if (json["isolate"]["id"] != isolateId) return; 122 | 123 | try { 124 | var result = onEvent(json); 125 | if (result == null || result == false) return; 126 | subscription.cancel(); 127 | completer.complete(result); 128 | } catch (error, stackTrace) { 129 | completer.completeError(error, stackTrace); 130 | } 131 | }, onError: (error, stackTrace) { 132 | subscription.cancel(); 133 | completer.completeError(error, stackTrace); 134 | }, onDone: () { 135 | // Throw and catch so the completer has a stack trace. 136 | try { 137 | throw new StateError("No element"); 138 | } catch (error, stackTrace) { 139 | completer.completeError(error, stackTrace); 140 | } 141 | }); 142 | 143 | var result = await immediate(); 144 | if (result != null && result != false) { 145 | subscription.cancel(); 146 | return result; 147 | } 148 | 149 | return await completer.future; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /lib/src/script.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:math' as math; 7 | 8 | import 'package:json_rpc_2/json_rpc_2.dart' as rpc; 9 | import 'package:source_span/source_span.dart'; 10 | 11 | import 'breakpoint.dart'; 12 | import 'class.dart'; 13 | import 'library.dart'; 14 | import 'object.dart'; 15 | import 'scope.dart'; 16 | import 'source_location.dart'; 17 | import 'source_report.dart'; 18 | 19 | VMScriptRef newVMScriptRef(Scope scope, Map json) { 20 | if (json == null) return null; 21 | assert(json["type"] == "@Script" || json["type"] == "Script"); 22 | return new VMScriptRef._(scope, json); 23 | } 24 | 25 | VMScriptToken newVMScriptToken( 26 | String isolateId, String scriptId, int position) { 27 | if (position == null) return null; 28 | return new VMScriptToken._(isolateId, scriptId, position); 29 | } 30 | 31 | VMScriptToken newVMScriptTokenFromPosition(VMScriptRef script, int position) { 32 | if (position == null) return null; 33 | return new VMScriptToken._(script._scope.isolateId, script._id, position); 34 | } 35 | 36 | /// A reference to a script in the Dart VM. 37 | /// 38 | /// A script contains information about the actual text of a library. Usually 39 | /// there's only one script per library, but the `part` directive can produce 40 | /// libraries made up of multiple scripts. 41 | class VMScriptRef implements VMObjectRef { 42 | final Scope _scope; 43 | 44 | /// The ID for script library, which is unique relative to its isolate. 45 | final String _id; 46 | 47 | /// Whether [_id] is guaranteed to be the same for different VM service 48 | /// script objects that refer to the same script. 49 | final bool _fixedId; 50 | 51 | Uri get observatoryUrl => _scope.observatoryUrlFor(_id); 52 | 53 | /// The URI from which this script was loaded. 54 | final Uri uri; 55 | 56 | VMScriptRef._(this._scope, Map json) 57 | : _id = json["id"], 58 | _fixedId = json["fixedId"] ?? false, 59 | uri = Uri.parse(json["uri"]); 60 | 61 | Future load() async => 62 | new VMScript._(_scope, await _scope.loadObject(_id)); 63 | 64 | /// Adds a breakpoint at [line] (and optionally [column]) in this script. 65 | /// 66 | /// Both [line] and [column] are 1-based. 67 | Future addBreakpoint(int line, {int column}) async { 68 | var params = {"scriptId": _id, "line": line}; 69 | if (column != null) params["column"] = column; 70 | 71 | try { 72 | var response = await _scope.sendRequest("addBreakpoint", params); 73 | return newVMBreakpoint(_scope, response); 74 | } on rpc.RpcException catch (error) { 75 | // Error 102 indicates that the breakpoint couldn't be created. 76 | if (error.code == 102) return null; 77 | rethrow; 78 | } 79 | } 80 | 81 | /// Generates a set of reports tied to this script. 82 | /// 83 | /// If [includeCoverageReport] is `true`, the report includes code coverage 84 | /// information via [VMSourceReportRange.hits] and 85 | /// [VMSourceReportRange.misses] in [VMSourceReport.ranges]. Otherwise, 86 | /// these properties are `null`. 87 | /// 88 | /// If [includePossibleBreakpoints] is `true`, the report includes a list of 89 | /// token positions which correspond to possible breakpoints via 90 | /// [VMSourceReportRange.possibleBreakpoints] in [VMSourceReport.ranges]. 91 | /// Otherwise, [VMSourceReportRange.possibleBreakpoints] is `null`. 92 | /// 93 | /// If [forceCompile] is `true`, all functions in the range of the report 94 | /// will be compiled. If `false`, functions that are never used may not appear 95 | /// in [VMSourceReportRange.misses]. 96 | /// Forcing compilation can cause a compilation error, which could terminate 97 | /// the running Dart program. 98 | /// 99 | /// [location] can be provided to restrict analysis to a subrange of the 100 | /// script. An [ArgumentError] is thrown if `location.end` is `null`. 101 | Future getSourceReport( 102 | {bool includeCoverageReport: true, 103 | bool includePossibleBreakpoints: true, 104 | bool forceCompile: false, 105 | VMSourceLocation location}) async { 106 | var reports = []; 107 | if (includeCoverageReport) reports.add('Coverage'); 108 | if (includePossibleBreakpoints) reports.add('PossibleBreakpoints'); 109 | 110 | var params = {'scriptId': _id, 'reports': reports}; 111 | if (forceCompile) params['forceCompile'] = true; 112 | if (location != null) { 113 | if (location.end == null) { 114 | throw new ArgumentError.value( 115 | location, 'location', 'location.end cannot be null.'); 116 | } 117 | params['tokenPos'] = location.token._position; 118 | params['endTokenPos'] = location.end._position; 119 | } 120 | 121 | var json = await _scope.sendRequest('getSourceReport', params); 122 | return newSourceReport(_scope, json); 123 | } 124 | 125 | bool operator ==(other) => 126 | other is VMScriptRef && (_fixedId ? _id == other._id : super == other); 127 | 128 | int get hashCode => _fixedId ? _id.hashCode : super.hashCode; 129 | 130 | String toString() => uri.toString(); 131 | } 132 | 133 | /// A script in the Dart VM. 134 | class VMScript extends VMScriptRef implements VMObject { 135 | final VMClassRef klass; 136 | 137 | final int size; 138 | 139 | /// The library that owns this script. 140 | final VMLibraryRef library; 141 | 142 | /// The source code for this script. 143 | /// 144 | /// For certain built-in libraries, this may be reconstructed without source 145 | /// comments. 146 | final String source; 147 | 148 | /// A table encoding a mapping from token position to line and column. 149 | /// 150 | /// Each subarray consists of an int designating the line, followed by any 151 | /// number of position-column pairs that represent the positions of tokens 152 | /// known to the VM. 153 | /// 154 | /// Because this encodes all the known token positions, it's more efficient to 155 | /// access than the representation used by a [SourceFile] as long as you're 156 | /// looking up a known token boundary. 157 | final List> _tokenPositions; 158 | 159 | /// A source file that provides access to location and span information about 160 | /// this script. 161 | /// 162 | /// This is generally less efficient than calling [sourceSpan] and 163 | /// [sourceLocation] directly, and should only be used when you don't have 164 | /// [VMSourceLocation] or [VMScriptToken] objects. 165 | /// 166 | /// Note that [SourceFile] uses 0-based lines and columns, whereas the rest of 167 | /// this package uses 1-based lines and columns. 168 | SourceFile get sourceFile { 169 | _sourceFile ??= new SourceFile.fromString(source, url: uri); 170 | return _sourceFile; 171 | } 172 | 173 | SourceFile _sourceFile; 174 | 175 | VMScript._(Scope scope, Map json) 176 | : klass = newVMClassRef(scope, json["class"]), 177 | size = json["size"], 178 | library = newVMLibraryRef(scope, json["library"]), 179 | source = json["source"], 180 | _tokenPositions = (json["tokenPosTable"] as List) 181 | .map((sublist) => (sublist as List).cast()) 182 | .toList(), 183 | super._(scope, json); 184 | 185 | /// Returns a [FileSpan] representing the source covered by [location]. 186 | /// 187 | /// If [location] doesn't have a [VMSourceLocation.end] token, this will be a 188 | /// point span. Note that [FileSpan] uses 0-based lines and columns, whereas 189 | /// the rest of this package uses 1-based lines and columns. 190 | /// 191 | /// Throws an [ArgumentError] if [location] isn't for this script. 192 | FileSpan sourceSpan(VMSourceLocation location) { 193 | if (location.script._scope.isolateId != _scope.isolateId || 194 | (_fixedId && location.script._id != _id)) { 195 | throw new ArgumentError("SourceLocation isn't for this script."); 196 | } 197 | 198 | var end = location.end ?? location.token; 199 | return new _ScriptSpan(this, location.token._position, end._position); 200 | } 201 | 202 | /// Returns a [FileLocation] representing the location indicated by [token]. 203 | /// 204 | /// Note that [FileLocation] uses 0-based lines and columns, whereas the rest 205 | /// of this package uses 1-based lines and columns. 206 | /// 207 | /// Throws an [ArgumentError] if [token] isn't for this script. 208 | FileLocation sourceLocation(VMScriptToken token) { 209 | if (token._isolateId != _scope.isolateId || 210 | (_fixedId && token._scriptId != _id)) { 211 | throw new ArgumentError("Token isn't for this script."); 212 | } 213 | 214 | return new _ScriptLocation(this, token._position); 215 | } 216 | 217 | /// Binary searches [_tokenPositions] for the line and column information for 218 | /// the token at [position]. 219 | /// 220 | /// This returns a `line, column` pair if the token is found, and throws a 221 | /// [StateError] otherwise. 222 | List _lineAndColumn(int position) { 223 | var min = 0; 224 | var max = _tokenPositions.length; 225 | while (min < max) { 226 | var mid = min + ((max - min) >> 1); 227 | 228 | var row = _tokenPositions[mid]; 229 | 230 | if (row[1] > position) { 231 | max = mid; 232 | } else { 233 | for (var i = 1; i < row.length; i += 2) { 234 | if (row[i] == position) return [row.first, row[i + 1]]; 235 | } 236 | 237 | min = mid + 1; 238 | } 239 | } 240 | 241 | // We only call this for positions that come from the VM, so we shouldn't 242 | // ever actually reach this point. 243 | throw new StateError("Couldn't find line and column for token $position in " 244 | "$uri."); 245 | } 246 | } 247 | 248 | /// The location of a token in a Dart script. 249 | /// 250 | /// A token can be passed to [VMScript.sourceLocation] to get the line and 251 | /// column information for the token. 252 | class VMScriptToken { 253 | /// The ID of this token's script's isolate. 254 | final String _isolateId; 255 | 256 | /// The ID of this token's script. 257 | final String _scriptId; 258 | 259 | /// This is deprecated. 260 | /// 261 | /// Use `VMScript.sourceLocation().offset` instead. 262 | @Deprecated("Will be removed in 0.3.0.") 263 | int get offset => _position; 264 | final int _position; 265 | 266 | VMScriptToken._(this._isolateId, this._scriptId, this._position); 267 | 268 | String toString() => _position.toString(); 269 | } 270 | 271 | /// An implementation of [FileLocation] based on a known token position. 272 | class _ScriptLocation extends SourceLocationMixin implements FileLocation { 273 | /// The script that produced this location. 274 | final VMScript _script; 275 | 276 | /// The position of the token used to generate this location. 277 | /// 278 | /// This is opaque, but it can be used to look up the line and column 279 | /// information. 280 | final int _position; 281 | 282 | int get offset { 283 | // TODO(nweiz): Make this more efficient when sdk#26390 is fixed. 284 | _offset ??= _script.sourceFile.getOffset(line, column); 285 | return _offset; 286 | } 287 | 288 | int _offset; 289 | 290 | SourceFile get file => _script.sourceFile; 291 | 292 | Uri get sourceUrl => _script.uri; 293 | 294 | int get line { 295 | _ensureLineAndColumn(); 296 | return _line; 297 | } 298 | 299 | int _line; 300 | 301 | int get column { 302 | _ensureLineAndColumn(); 303 | return _column; 304 | } 305 | 306 | int _column; 307 | 308 | _ScriptLocation(this._script, this._position); 309 | 310 | /// Ensures that [_line] and [_column] are set based on [_script]'s 311 | /// information. 312 | void _ensureLineAndColumn() { 313 | if (_line != null) return null; 314 | var result = _script._lineAndColumn(_position); 315 | 316 | // SourceLocation lines and columns are zero-based, the VM service's are 317 | // 1-based. 318 | _line = result.first - 1; 319 | _column = result.last - 1; 320 | } 321 | 322 | int compareTo(SourceLocation other) => 323 | other is _ScriptLocation && other.sourceUrl == sourceUrl 324 | ? _position - other._position 325 | : super.compareTo(other); 326 | 327 | bool operator ==(other) => other is _ScriptLocation 328 | ? _position == other._position && sourceUrl == other.sourceUrl 329 | : super == other; 330 | 331 | FileSpan pointSpan() => 332 | new _ScriptSpan(_script, _position, _position, this, this); 333 | } 334 | 335 | /// An implementation of [FileSpan] based on known token positions. 336 | class _ScriptSpan extends SourceSpanMixin implements FileSpan { 337 | /// The script that produced this location. 338 | final VMScript _script; 339 | 340 | SourceFile get file => _script.sourceFile; 341 | 342 | /// The position of the start token. 343 | final int _startPosition; 344 | 345 | /// The position of the end token. 346 | final int _endPosition; 347 | 348 | Uri get sourceUrl => _script.uri; 349 | 350 | int get length { 351 | // Prefer checking just the lines and columns, since they're more efficient 352 | // to access. 353 | if (start.line == end.line) return end.column - start.column; 354 | return end.offset - start.offset; 355 | } 356 | 357 | FileLocation get start { 358 | _start ??= new _ScriptLocation(_script, _startPosition); 359 | return _start; 360 | } 361 | 362 | FileLocation _start; 363 | 364 | FileLocation get end { 365 | _end ??= new _ScriptLocation(_script, _endPosition); 366 | return _end; 367 | } 368 | 369 | FileLocation _end; 370 | 371 | String get text => _script.source.substring(start.offset, end.offset); 372 | 373 | String get context => file.getText(file.getOffset(start.line), 374 | end.line == file.lines - 1 ? null : file.getOffset(end.line + 1)); 375 | 376 | _ScriptSpan(this._script, this._startPosition, this._endPosition, 377 | [this._start, this._end]); 378 | 379 | int compareTo(SourceSpan other) { 380 | if (other is! _ScriptSpan || other.sourceUrl != sourceUrl) { 381 | return super.compareTo(other); 382 | } 383 | 384 | _ScriptSpan otherFile = other; 385 | var result = _startPosition.compareTo(otherFile._startPosition); 386 | return result == 0 387 | ? _endPosition.compareTo(otherFile._endPosition) 388 | : result; 389 | } 390 | 391 | SourceSpan union(SourceSpan other) { 392 | if (other is _ScriptSpan) { 393 | var span = expand(other) as _ScriptSpan; 394 | var beginSpan = span._startPosition == _startPosition ? this : other; 395 | var endSpan = span._endPosition == _endPosition ? this : other; 396 | 397 | if (beginSpan._endPosition < endSpan._startPosition) { 398 | throw new ArgumentError("Spans $this and $other are disjoint."); 399 | } 400 | 401 | return span; 402 | } 403 | return super.union(other); 404 | } 405 | 406 | bool operator ==(other) => other is _ScriptSpan 407 | ? _startPosition == other._startPosition && 408 | _endPosition == other._endPosition && 409 | sourceUrl == other.sourceUrl 410 | : super == other; 411 | 412 | FileSpan expand(FileSpan other) { 413 | if (sourceUrl != other.sourceUrl) { 414 | throw new ArgumentError("Source URLs \"${sourceUrl}\" and " 415 | " \"${other.sourceUrl}\" don't match."); 416 | } 417 | 418 | if (other is _ScriptSpan) { 419 | var startPosition = math.min(this._startPosition, other._startPosition); 420 | var endPosition = math.max(this._endPosition, other._endPosition); 421 | return new _ScriptSpan( 422 | _script, 423 | startPosition, 424 | endPosition, 425 | startPosition == this._startPosition ? this._start : other._start, 426 | endPosition == this._endPosition ? this._end : other._end); 427 | } else { 428 | var start = math.min(this.start.offset, other.start.offset); 429 | var end = math.max(this.end.offset, other.end.offset); 430 | return file.span(start, end); 431 | } 432 | } 433 | } 434 | -------------------------------------------------------------------------------- /lib/src/sentinel.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | VMSentinel newVMSentinel(Map json) { 6 | assert(json["type"] == "Sentinel"); 7 | switch (json["kind"]) { 8 | case "Collected": 9 | return VMSentinel.collected; 10 | case "Expired": 11 | return VMSentinel.expired; 12 | case "NotInitialized": 13 | return VMSentinel.notInitialized; 14 | case "BeingInitialized": 15 | return VMSentinel.beingInitialized; 16 | case "OptimizedOut": 17 | return VMSentinel.optimizedOut; 18 | case "Free": 19 | return VMSentinel.free; 20 | default: 21 | throw new StateError('Unknown Sentinel kind "${json["kind"]}".'); 22 | } 23 | } 24 | 25 | /// A value that indicates that an object on the remote VM is no longer 26 | /// available. 27 | class VMSentinel { 28 | /// The object has been collected by the GC. 29 | static const collected = const VMSentinel._("collected"); 30 | 31 | /// The object's ID has expired. 32 | static const expired = const VMSentinel._("expired"); 33 | 34 | /// The variable or field hasn't been initialized. 35 | static const notInitialized = const VMSentinel._("not initialized"); 36 | 37 | /// The variable or field is in the process of being initialized. 38 | static const beingInitialized = const VMSentinel._("being initialized"); 39 | 40 | /// The variable has been eliminated by the optimizing compiler. 41 | static const optimizedOut = const VMSentinel._("optimized out"); 42 | 43 | /// Reserved for future use. 44 | static const free = const VMSentinel._("free"); 45 | 46 | /// The human-readable name of the sentinel. 47 | final String _name; 48 | 49 | const VMSentinel._(this._name); 50 | 51 | String toString() => "<$_name>"; 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/service_version.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | VMServiceVersion newVMServiceVersion(Map json) { 6 | if (json == null) return null; 7 | assert(json["type"] == "Version"); 8 | return new VMServiceVersion._(json); 9 | } 10 | 11 | /// The version of the VM service protocol (*not* the Dart VM itself). 12 | class VMServiceVersion { 13 | /// The major version. 14 | /// 15 | /// This is incremented when the protocol is changed in a potentially-breaking 16 | /// way. 17 | final int major; 18 | 19 | /// The minor version. 20 | /// 21 | /// This is incremented when the protocol is changed in a backwards-compatible 22 | /// way. 23 | final int minor; 24 | 25 | VMServiceVersion._(Map json) 26 | : major = json["major"], 27 | minor = json["minor"]; 28 | 29 | String toString() => "$major.$minor"; 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/source_location.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'breakpoint.dart'; 6 | import 'scope.dart'; 7 | import 'script.dart'; 8 | 9 | VMSourceLocation newVMSourceLocation(Scope scope, Map json) { 10 | if (json == null) return null; 11 | assert(json["type"] == "SourceLocation"); 12 | return new VMSourceLocation._(scope, json); 13 | } 14 | 15 | VMSourceLocation newVMSourceLocationFromPosition( 16 | VMScriptRef script, int tokenPos, int endTokenPos) => 17 | new VMSourceLocation._fromPositions(script, tokenPos, endTokenPos); 18 | 19 | /// A location or span of code in a Dart script. 20 | class VMSourceLocation implements VMBreakpointLocation { 21 | /// The script containing the source location. 22 | final VMScriptRef script; 23 | 24 | /// The canonical source URI of [script]. 25 | Uri get uri => script.uri; 26 | 27 | /// If this is a span, this represents the start of that span. 28 | final VMScriptToken token; 29 | 30 | /// The final token of the location, or `null` if this is not a span. 31 | final VMScriptToken end; 32 | 33 | VMSourceLocation._(Scope scope, Map json) 34 | : script = newVMScriptRef(scope, json["script"]), 35 | token = newVMScriptToken( 36 | scope.isolateId, json["script"]["id"], json["tokenPos"]), 37 | end = newVMScriptToken( 38 | scope.isolateId, json["script"]["id"], json["endTokenPos"]); 39 | 40 | VMSourceLocation._fromPositions( 41 | VMScriptRef script, int tokenPos, int endTokenPos) 42 | : this.script = script, 43 | token = newVMScriptTokenFromPosition(script, tokenPos), 44 | end = newVMScriptTokenFromPosition(script, endTokenPos); 45 | 46 | String toString() => 47 | end == null ? "$script at $token" : "$script from $token to $end"; 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/source_report.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'script.dart'; 6 | import 'scope.dart'; 7 | import 'source_location.dart'; 8 | 9 | VMSourceReport newSourceReport(Scope scope, Map json) { 10 | if (json == null) return null; 11 | assert(json["type"] == "SourceReport"); 12 | 13 | var scripts = (json['scripts'] as List) 14 | .map((script) => newVMScriptRef(scope, script)) 15 | .toList(); 16 | 17 | var ranges = new List.unmodifiable( 18 | (json['ranges'] as List).map((rangeItem) => 19 | new VMSourceReportRange._fromJson(rangeItem, scripts))); 20 | 21 | return new VMSourceReport._(ranges); 22 | } 23 | 24 | /// A report about ranges of source code. 25 | /// 26 | /// [ranges] exposes code coverage and information on possible breakpoints. 27 | class VMSourceReport { 28 | /// Ranges in the program source corresponding to ranges of executable code in 29 | /// the user's program. 30 | /// 31 | /// Ranges can include members such as functions, methods, and 32 | /// constructors. 33 | /// 34 | /// Note: ranges may nest in other ranges, in the case of nested functions. 35 | /// Ranges may be duplicated, in the case of mixins. 36 | final List ranges; 37 | 38 | VMSourceReport._(this.ranges); 39 | } 40 | 41 | /// A range of executable code (function, method, constructor, etc) 42 | /// in the running program. 43 | /// 44 | /// This is part of a [VMSourceReport]. 45 | class VMSourceReportRange { 46 | VMScriptRef get script => location.script; 47 | 48 | /// The location of the range in the target script. 49 | final VMSourceLocation location; 50 | 51 | /// `true` if this range been compiled by the Dart VM. 52 | /// 53 | /// If `false`, `possibleBreakpoints`, `hits`, and `misses` will be `null`. 54 | /// 55 | /// Use `forceCompile` in `getSourceReport` to ensure the target scripts are 56 | /// compiled. 57 | final bool compiled; 58 | 59 | /// Token positions at which breakpoints can be set. 60 | /// 61 | /// Provided only when `includePossibleBreakpoints` in `getSourceReport` 62 | /// is `true` and the range has been compiled. 63 | final List possibleBreakpoints; 64 | 65 | /// Token positions in this range which have been executed. 66 | /// 67 | /// Provided only when `includeCoverageReport` in `getSourceReport` is `true` 68 | /// and the range has been compiled. 69 | /// 70 | /// Sorted by the position in the source file, starting with the first hit. 71 | final List hits; 72 | 73 | /// Token positions in this range which have not yet been executed. 74 | /// 75 | /// Provided only when `includeCoverageReport` in `getSourceReport` is `true` 76 | /// and the range has been compiled. 77 | /// 78 | /// Sorted by the location in the source file, starting with the first miss. 79 | final List misses; 80 | 81 | /// Creates a new [VMSourceReportRange]. 82 | /// 83 | /// [json] corresponds to data returned by the `getSourceReport` RPC. 84 | /// 85 | /// [scripts] is used to find the [VMScriptRef] referenced by index in [json]. 86 | factory VMSourceReportRange._fromJson(Map json, List scripts) { 87 | var script = scripts[json['scriptIndex']]; 88 | 89 | var location = newVMSourceLocationFromPosition( 90 | script, json['startPos'], json['endPos']); 91 | 92 | var compiled = json['compiled']; 93 | 94 | var hits = json['coverage'] == null 95 | ? null 96 | : _getTokens(script, List.castFrom(json['coverage']['hits'])); 97 | 98 | var misses = json['coverage'] == null 99 | ? null 100 | : _getTokens(script, List.castFrom(json['coverage']['misses'])); 101 | 102 | var possibleBreakpoints = json['possibleBreakpoints'] == null 103 | ? null 104 | : _getTokens(script, List.castFrom(json['possibleBreakpoints'])); 105 | 106 | return new VMSourceReportRange._( 107 | compiled, hits, misses, possibleBreakpoints, location); 108 | } 109 | 110 | VMSourceReportRange._(this.compiled, this.hits, this.misses, 111 | this.possibleBreakpoints, this.location); 112 | 113 | /// Returns an unmodifiable [List] corresponding to the 114 | /// provided token [locations]. 115 | static List _getTokens( 116 | VMScriptRef script, Iterable locations) { 117 | return new List.unmodifiable(locations 118 | .map((position) => newVMScriptTokenFromPosition(script, position))); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/src/stack.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:stack_trace/stack_trace.dart'; 8 | 9 | import 'frame.dart'; 10 | import 'message.dart'; 11 | import 'scope.dart'; 12 | import 'script.dart'; 13 | import 'utils.dart'; 14 | 15 | VMStack newVMStack(Scope scope, Map json) { 16 | if (json == null) return null; 17 | assert(json["type"] == "Stack"); 18 | return new VMStack._(scope, json); 19 | } 20 | 21 | /// The current execution stack and message queue for an isolate. 22 | class VMStack { 23 | /// The current execution stack. 24 | /// 25 | /// The earlier a frame appears in this list, the closer to the current point 26 | /// of execution. This will be empty if the isolate is not currently executing 27 | /// any Dart code. 28 | final List frames; 29 | 30 | /// The current message queue. 31 | /// 32 | /// The earlier a message appears in this list, the earlier it will be 33 | /// processed. 34 | final List messages; 35 | 36 | VMStack._(Scope scope, Map json) 37 | : frames = new List.unmodifiable( 38 | json["frames"].map((frame) => newVMFrame(scope, frame))), 39 | messages = new List.unmodifiable( 40 | json["messages"].map((message) => newVMMessage(scope, message))); 41 | 42 | /// Returns the trace of this stack. 43 | Future getTrace() async { 44 | var scripts = {}; 45 | return new Trace( 46 | await Future.wait(frames.map((frame) => frameToFrame(frame, scripts)))); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/stream_manager.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:json_rpc_2/json_rpc_2.dart' as rpc; 8 | 9 | /// A class that exposes the service protocol's event streams. 10 | /// 11 | /// This ensures that the protocol only sends events for streams that have 12 | /// listeners. It doesn't do any parsing of the events, and exposes them as raw 13 | /// JSON maps. 14 | /// 15 | /// All streams are exposed as broadcast streams. 16 | class StreamManager { 17 | /// The underlying JSON-RPC 2.0 peer used to communicate with the VM service. 18 | final rpc.Peer _peer; 19 | 20 | /// Events related to the whole VM. 21 | Stream get vm => _vmController.stream; 22 | StreamController _vmController; 23 | 24 | /// Events related to isolate lifecycles. 25 | Stream get isolate => _isolateController.stream; 26 | StreamController _isolateController; 27 | 28 | /// Events related to debugging. 29 | Stream get debug => _debugController.stream; 30 | StreamController _debugController; 31 | 32 | /// Garbage collection events. 33 | Stream get gc => _gcController.stream; 34 | StreamController _gcController; 35 | 36 | /// Data written to standard output. 37 | Stream get stdout => _stdoutController.stream; 38 | StreamController _stdoutController; 39 | 40 | /// Data written to standard error. 41 | Stream get stderr => _stderrController.stream; 42 | StreamController _stderrController; 43 | 44 | /// Custom events posted using `postEvent` from `dart:developer`. 45 | Stream get extension => _extensionController.stream; 46 | StreamController _extensionController; 47 | 48 | /// A subscription to [debug]. 49 | /// 50 | /// This subscription fires no events, but it exists as long as there's also a 51 | /// subscription to [stdout] or [stderr] to work around sdk#24350. 52 | StreamSubscription _debugSubscription; 53 | 54 | StreamManager(this._peer) { 55 | _isolateController = _controller("Isolate"); 56 | _vmController = _controller("VM"); 57 | _debugController = _controller("Debug"); 58 | _gcController = _controller("GC"); 59 | _stdoutController = _controller("Stdout"); 60 | _stderrController = _controller("Stderr"); 61 | _extensionController = _controller("Extension"); 62 | 63 | _peer.registerMethod("streamNotify", (params) { 64 | switch (params["streamId"].asString) { 65 | case "VM": 66 | _vmController.add(params["event"].asMap); 67 | break; 68 | case "Isolate": 69 | _isolateController.add(params["event"].asMap); 70 | break; 71 | case "Debug": 72 | _debugController.add(params["event"].asMap); 73 | break; 74 | case "GC": 75 | _gcController.add(params["event"].asMap); 76 | break; 77 | case "Stdout": 78 | _stdoutController.add(params["event"].asMap); 79 | break; 80 | case "Stderr": 81 | _stderrController.add(params["event"].asMap); 82 | break; 83 | case "Extension": 84 | _extensionController.add(params["event"].asMap); 85 | break; 86 | } 87 | }); 88 | 89 | _peer.done.then((_) { 90 | _vmController.close(); 91 | _isolateController.close(); 92 | _debugController.close(); 93 | _gcController.close(); 94 | _stderrController.close(); 95 | _stdoutController.close(); 96 | _extensionController.close(); 97 | }, onError: (_) {}); 98 | } 99 | 100 | /// Returns a broadcast [StreamController] for the stream with [streamID]. 101 | /// 102 | /// This controller subscribes to the stream when it has a listener and 103 | /// unsubscribes once it has no listeners. 104 | StreamController _controller(String streamID) { 105 | StreamController controller; 106 | controller = new StreamController.broadcast( 107 | sync: true, 108 | onListen: () { 109 | // Work around sdk#24350. 110 | if ((streamID == "Stdout" || streamID == "Stderr") && 111 | _debugSubscription == null) { 112 | _debugSubscription = debug.listen(null); 113 | } 114 | 115 | _peer.sendRequest("streamListen", {"streamId": streamID}).catchError( 116 | (error, stackTrace) { 117 | controller.addError(error, stackTrace); 118 | }); 119 | }, 120 | onCancel: () { 121 | if (_peer.isClosed) return; 122 | 123 | // Work around sdk#24350. 124 | if (_debugSubscription != null && 125 | !_stdoutController.hasListener && 126 | !_stderrController.hasListener) { 127 | _debugSubscription.cancel(); 128 | _debugSubscription = null; 129 | } 130 | 131 | _peer.sendRequest("streamCancel", {"streamId": streamID}).catchError( 132 | (_) { 133 | // Do nothing if canceling the stream failed, since no one's listening 134 | // to it anyway. 135 | }); 136 | }); 137 | return controller; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /lib/src/type_arguments.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'class.dart'; 8 | import 'instance.dart'; 9 | import 'object.dart'; 10 | import 'scope.dart'; 11 | 12 | VMTypeArgumentsRef newVMTypeArgumentsRef(Scope scope, Map json) { 13 | if (json == null) return null; 14 | assert(json["type"] == "@TypeArguments" || json["type"] == "TypeArguments"); 15 | return new VMTypeArgumentsRef._(scope, json); 16 | } 17 | 18 | /// A reference to the type arguments for some instantiated generic type. 19 | class VMTypeArgumentsRef implements VMObjectRef { 20 | final Scope _scope; 21 | 22 | /// The ID for these type arguments, which is unique relative to their 23 | /// isolate. 24 | final String _id; 25 | 26 | /// Whether [_id] is guaranteed to be the same for different VM service type 27 | /// arguments objects that refer to the same type arguments. 28 | final bool _fixedId; 29 | 30 | Uri get observatoryUrl => _scope.observatoryUrlFor(_id); 31 | 32 | /// A name for this type argument list. 33 | final String name; 34 | 35 | VMTypeArgumentsRef._(this._scope, Map json) 36 | : _id = json["id"], 37 | _fixedId = json["fixedId"] ?? false, 38 | name = json["name"]; 39 | 40 | Future load() async => 41 | new VMTypeArguments._(_scope, await _scope.loadObject(_id)); 42 | 43 | bool operator ==(other) => 44 | other is VMTypeArgumentsRef && 45 | (_fixedId ? _id == other._id : super == other); 46 | 47 | int get hashCode => _fixedId ? _id.hashCode : super.hashCode; 48 | 49 | String toString() => name; 50 | } 51 | 52 | /// The type arguments for some instantiated generic type. 53 | class VMTypeArguments extends VMTypeArgumentsRef implements VMObject { 54 | final VMClassRef klass; 55 | 56 | final int size; 57 | 58 | /// The type arguments. 59 | final List types; 60 | 61 | VMTypeArguments._(Scope scope, Map json) 62 | : klass = newVMClassRef(scope, json["class"]), 63 | size = json["size"], 64 | types = new List.unmodifiable( 65 | json["types"].map((type) => newVMTypeLikeInstanceRef(scope, type))), 66 | super._(scope, json); 67 | } 68 | -------------------------------------------------------------------------------- /lib/src/unresolved_source_location.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'breakpoint.dart'; 6 | import 'scope.dart'; 7 | import 'script.dart'; 8 | 9 | VMUnresolvedSourceLocation newVMUnresolvedSourceLocation( 10 | Scope scope, Map json) { 11 | if (json == null) return null; 12 | assert(json["type"] == "UnresolvedSourceLocation"); 13 | return new VMUnresolvedSourceLocation._(scope, json); 14 | } 15 | 16 | /// The location of an unresolved breakpoint. 17 | class VMUnresolvedSourceLocation implements VMBreakpointLocation { 18 | final VMScriptRef script; 19 | 20 | final Uri uri; 21 | 22 | /// If this is `null`, [line] and [column] are guaranteed to be non-`null`. 23 | final VMScriptToken token; 24 | 25 | /// The line specified by the user when they created the breakpoint. 26 | /// 27 | /// This is 1-based. It's `null` if and only if [token] is non-`null`. 28 | final int line; 29 | 30 | /// The column specified by the user when they created the breakpoint. 31 | /// 32 | /// This is 1-based. It's `null` if [token] is non-`null` or if the user 33 | /// didn't specify a column. 34 | final int column; 35 | 36 | VMUnresolvedSourceLocation._(Scope scope, Map json) 37 | : script = newVMScriptRef(scope, json["script"]), 38 | uri = Uri.parse(json["scriptUri"] == null 39 | ? json["script"]["uri"] 40 | : json["scriptUri"]), 41 | token = json["tokenPos"] == null 42 | ? null 43 | : newVMScriptToken( 44 | scope.isolateId, json["script"]["id"], json["tokenPos"]), 45 | line = json["line"], 46 | column = json["column"]; 47 | 48 | String toString() { 49 | if (token != null) return "$uri at $token"; 50 | return column == null ? "$uri at $line" : "$uri at $line:$column"; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:stack_trace/stack_trace.dart'; 8 | 9 | import 'class.dart'; 10 | import 'frame.dart'; 11 | import 'function.dart'; 12 | import 'object.dart'; 13 | import 'script.dart'; 14 | 15 | /// A [Map] between whitespace characters and their escape sequences. 16 | const _escapeMap = const { 17 | '\n': r'\n', 18 | '\r': r'\r', 19 | '\f': r'\f', 20 | '\b': r'\b', 21 | '\t': r'\t', 22 | '\v': r'\v', 23 | '\x7F': r'\x7F', // delete 24 | }; 25 | 26 | /// A regular expression matching the Dart VM stack frame method name for an 27 | /// asynchronous body. 28 | final _asyncBody = new RegExp(r'<(|[^>]+)_async_body>'); 29 | 30 | /// Transforms [stream] with a [StreamTransformer] that transforms data events 31 | /// using [handleData]. 32 | Stream transform( 33 | Stream stream, void handleData(S data, EventSink sink)) => 34 | stream 35 | .transform(new StreamTransformer.fromHandlers(handleData: handleData)); 36 | 37 | /// Loads a `stack_trace` [Frame] for [frame]. 38 | /// 39 | /// If [scripts] is passed, it should be a map of [VMScript]s that have already 40 | /// been loaded, indexed by [name]. This function modifys the map so that it can 41 | /// be passed in to future invocations to avoid unnecessary loads. 42 | Future frameToFrame(VMFrame frame, 43 | [Map scripts]) async { 44 | var scopes = []; 45 | VMObjectRef scope = frame.function; 46 | while (scope is VMFunctionRef) { 47 | var function = scope as VMFunctionRef; 48 | scopes.add(function.name.replaceAll(_asyncBody, "")); 49 | scope = function.owner; 50 | } 51 | if (scope is VMClassRef) scopes.add(scope.name); 52 | var member = scopes.reversed.join("."); 53 | 54 | var uri = frame.location.script.uri; 55 | var script = scripts == null ? null : scripts[uri.toString()]; 56 | if (script == null) { 57 | script = await frame.location.script.load(); 58 | 59 | // The special "evaluate" scheme is used for evaluating code with the VM 60 | // service. Different scripts can have the same "evalute" scheme, so we 61 | // don't record them. 62 | if (scripts != null && uri.scheme != 'evaluate') 63 | scripts[uri.toString()] = script; 64 | } 65 | var location = await script.sourceLocation(frame.location.token); 66 | 67 | return new Frame(script.uri, location.line, location.column, member); 68 | } 69 | 70 | /// A [RegExp] that matches whitespace characters that should be escaped. 71 | final _escapeRegExp = new RegExp( 72 | "[\\x00-\\x07\\x0E-\\x1F${_escapeMap.keys.map(_getHexLiteral).join()}]"); 73 | 74 | /// Returns [str] with all whitespace characters represented as their escape 75 | /// sequences. 76 | /// 77 | /// Backslash characters are escaped as `\\` 78 | String escapeString(String str) { 79 | str = str.replaceAll('\\', r'\\'); 80 | return str.replaceAllMapped(_escapeRegExp, (match) { 81 | var mapped = _escapeMap[match[0]]; 82 | if (mapped != null) return mapped; 83 | return _getHexLiteral(match[0]); 84 | }); 85 | } 86 | 87 | /// Given single-character string, return the hex-escaped equivalent. 88 | String _getHexLiteral(String input) { 89 | int rune = input.runes.single; 90 | return r'\x' + rune.toRadixString(16).toUpperCase().padLeft(2, '0'); 91 | } 92 | -------------------------------------------------------------------------------- /lib/src/v1_compatibility.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | const v1CompatibilityTransformer = const _V1CompatibilityTransformer(); 8 | 9 | class _V1CompatibilityTransformer extends StreamTransformerBase { 10 | const _V1CompatibilityTransformer(); 11 | 12 | Stream bind(Stream stream) { 13 | return stream.map((event) { 14 | // The V1 protocol didn't support batching. 15 | if (event is! Map) return event; 16 | 17 | // The V1 protocol never included the proper "jsonrpc" key. 18 | if (event.containsKey("jsonrpc")) return event; 19 | 20 | event = new Map.from(event); 21 | 22 | // Some V1 protocol events did include an incorrect "json-rpc" key. 23 | event.remove("jsonrpc"); 24 | event["json-rpc"] = "2.0"; 25 | 26 | // The V1 protocol used a non-standard event format. 27 | if (event.containsKey("event")) { 28 | event["method"] = "streamNotify"; 29 | event["params"] = { 30 | "streamId": event.remove("streamId"), 31 | "event": event.remove("event") 32 | }; 33 | } 34 | 35 | // The V1 protocol converted ints to strings. The json_rpc_2 package 36 | // always uses int IDs. 37 | event["id"] = int.parse(event["id"]); 38 | 39 | return event; 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/vm.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:json_rpc_2/json_rpc_2.dart' as rpc; 8 | import 'package:pub_semver/pub_semver.dart'; 9 | 10 | import 'isolate.dart'; 11 | import 'stream_manager.dart'; 12 | import 'utils.dart'; 13 | 14 | VM newVM(rpc.Peer peer, StreamManager streams, Map json) { 15 | if (json == null) return null; 16 | assert(json["type"] == "VM"); 17 | return new VM._(peer, streams, json); 18 | } 19 | 20 | /// A reference to data the Dart VM as a whole. 21 | /// 22 | /// The full VM with additional metadata can be loaded using [load]. 23 | class VMRef { 24 | /// The underlying JSON-RPC peer used to communicate with the VM service. 25 | final rpc.Peer _peer; 26 | 27 | /// The streams shared among the entire service protocol client. 28 | final StreamManager _streams; 29 | 30 | /// A name identifying this VM for debugging. 31 | /// 32 | /// This isn't guaranteed to be unique. It can be set using [setName]. This is 33 | /// only supported on the VM service protocol version 3.0 and greater. 34 | final String name; 35 | 36 | /// A broadcast stream that emits a new reference to the VM every time its 37 | /// metadata changes. 38 | /// 39 | /// This is only supported on the VM service protocol version 3.0 and greater. 40 | Stream get onUpdate => _onUpdate; 41 | Stream _onUpdate; 42 | 43 | VMRef._(rpc.Peer peer, StreamManager streams, Map json) 44 | : _peer = peer, 45 | _streams = streams, 46 | name = json["name"] { 47 | _onUpdate = transform(_streams.vm, (json, sink) { 48 | if (json["kind"] != "VMUpdate") return; 49 | sink.add(new VMRef._(peer, streams, json["vm"])); 50 | }); 51 | } 52 | 53 | /// Sets the debugging [name] of the VM. 54 | /// 55 | /// Note that since this object is immutable, it needs to be reloaded to see 56 | /// the new name. 57 | Future setName(String name) => _peer.sendRequest("setVMName", {"name": name}); 58 | 59 | /// Loads the full representation of the VM. 60 | Future load() async => 61 | new VM._(_peer, _streams, await _peer.sendRequest("getVM", {})); 62 | } 63 | 64 | /// Data about the Dart VM as a whole. 65 | class VM extends VMRef { 66 | /// The word length of the target architecture, in bits. 67 | final int architectureBits; 68 | 69 | /// The name of the CPU for which the VM is generating code. 70 | final String targetCpu; 71 | 72 | /// The name of the CPU on which VM is actually running code. 73 | final String hostCpu; 74 | 75 | /// The semantic version of the Dart VM. 76 | /// 77 | /// Note that this is distinct from the VM service protocol version, which is 78 | /// accessible via [VMServiceClient.getVersion]. 79 | final Version version; 80 | 81 | /// The full version string of the Dart VM. 82 | /// 83 | /// This includes more information than [version] alone. 84 | final String versionString; 85 | 86 | /// The process ID of the VM process. 87 | final int pid; 88 | 89 | /// The time at which the VM started running. 90 | final DateTime startTime; 91 | 92 | /// The currently-running isolates. 93 | final List isolates; 94 | 95 | VM._(rpc.Peer peer, StreamManager streams, Map json) 96 | : architectureBits = json["architectureBits"], 97 | targetCpu = json["targetCPU"], 98 | hostCpu = json["hostCPU"], 99 | version = new Version.parse(json["version"].split(" ").first), 100 | versionString = json["version"], 101 | // Prior to the service protocol v3.0, the pid was sent as a string. 102 | // Afterwards, it was sent as an int. 103 | pid = json["pid"] is String ? int.parse(json["pid"]) : json["pid"], 104 | startTime = new DateTime.fromMillisecondsSinceEpoch( 105 | // Prior to v3.0, this was emitted as a double rather than an int. 106 | json["startTime"].round()), 107 | isolates = new List.unmodifiable(json["isolates"] 108 | .map((isolate) => newVMIsolateRef(peer, streams, isolate))), 109 | super._(peer, streams, json); 110 | } 111 | -------------------------------------------------------------------------------- /lib/vm_service_client.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @Deprecated('Use package:vm_service instead.') 6 | library vm_service_client; 7 | 8 | import 'dart:async'; 9 | 10 | import 'package:json_rpc_2/json_rpc_2.dart' as rpc; 11 | import 'package:stream_channel/stream_channel.dart'; 12 | import 'package:web_socket_channel/io.dart'; 13 | 14 | import 'src/exceptions.dart'; 15 | import 'src/flag.dart'; 16 | import 'src/isolate.dart'; 17 | import 'src/service_version.dart'; 18 | import 'src/stream_manager.dart'; 19 | import 'src/utils.dart'; 20 | import 'src/v1_compatibility.dart'; 21 | import 'src/vm.dart'; 22 | 23 | export 'src/bound_field.dart' hide newVMBoundField; 24 | export 'src/breakpoint.dart' hide newVMBreakpoint; 25 | export 'src/class.dart' hide newVMClassRef; 26 | export 'src/code.dart' hide newVMCodeRef; 27 | export 'src/context.dart' hide newVMContextRef; 28 | export 'src/error.dart' hide newVMError, newVMErrorRef; 29 | export 'src/exceptions.dart'; 30 | export 'src/field.dart' hide newVMFieldRef; 31 | export 'src/flag.dart' hide newVMFlagList; 32 | export 'src/frame.dart' hide newVMFrame; 33 | export 'src/function.dart' hide newVMFunctionRef; 34 | export 'src/instance.dart' 35 | hide 36 | newVMInstanceRef, 37 | newVMInstance, 38 | newVMTypeLikeInstanceRef, 39 | newVMTypeInstanceRef, 40 | newVMInstanceRefOrSentinel; 41 | export 'src/isolate.dart' hide newVMIsolateRef; 42 | export 'src/library.dart' hide newVMLibraryRef; 43 | export 'src/message.dart' hide newVMMessage; 44 | export 'src/object.dart'; 45 | export 'src/pause_event.dart' hide newVMPauseEvent; 46 | export 'src/script.dart' 47 | hide newVMScriptRef, newVMScriptToken, newVMScriptTokenFromPosition; 48 | export 'src/sentinel.dart' hide newVMSentinel; 49 | export 'src/service_version.dart' hide newVMServiceVersion; 50 | export 'src/source_location.dart' 51 | hide newVMSourceLocation, newVMSourceLocationFromPosition; 52 | export 'src/source_report.dart' hide newSourceReport; 53 | export 'src/stack.dart' hide newVMStack; 54 | export 'src/type_arguments.dart' hide newVMTypeArgumentsRef; 55 | export 'src/unresolved_source_location.dart' hide newVMUnresolvedSourceLocation; 56 | export 'src/vm.dart' hide newVM; 57 | 58 | /// A client for the [Dart VM service protocol][service api]. 59 | /// 60 | /// [service api]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md 61 | /// 62 | /// Connect to a VM service endpoint using [connect], and use [getVM] to load 63 | /// information about the VM itself. 64 | /// 65 | /// The client supports VM service versions 1.x (which first shipped with Dart 66 | /// 1.11), 2.x (which first shipped with Dart 1.12), and 3.x (which first 67 | /// shipped with Dart 1.13). Some functionality may be unavailable in older VM 68 | /// service versions; those places will be clearly documented. You can check the 69 | /// version of the VM service you're connected to using [getVersion]. 70 | /// 71 | /// Because it takes an extra RPC call to verify compatibility with the protocol 72 | /// version, the client doesn't do so by default. Users who want to be sure 73 | /// they're talking to a supported protocol version can call [validateVersion]. 74 | class VMServiceClient { 75 | /// The underlying JSON-RPC peer used to communicate with the VM service. 76 | final rpc.Peer _peer; 77 | 78 | /// The streams shared among the entire service protocol client. 79 | final StreamManager _streams; 80 | 81 | /// A broadcast stream that emits every isolate as it starts. 82 | Stream get onIsolateStart => _onIsolateStart; 83 | Stream _onIsolateStart; 84 | 85 | /// A broadcast stream that emits every isolate as it becomes runnable. 86 | /// 87 | /// These isolates are guaranteed to return a [VMRunnableIsolate] from 88 | /// [VMIsolateRef.load]. 89 | /// 90 | /// This is only supported on the VM service protocol version 3.0 and greater. 91 | Stream get onIsolateRunnable => _onIsolateRunnable; 92 | Stream _onIsolateRunnable; 93 | 94 | /// A future that fires when the underlying connection has been closed. 95 | /// 96 | /// Any connection-level errors will also be emitted through this future. 97 | final Future done; 98 | 99 | /// Connects to the VM service protocol at [url]. 100 | /// 101 | /// [url] may be a `ws://` or a `http://` URL. If it's `ws://`, it's 102 | /// interpreted as the URL to connect to directly. If it's `http://`, it's 103 | /// interpreted as the URL for the Dart observatory, and the corresponding 104 | /// WebSocket URL is determined based on that. It may be either a [String] or 105 | /// a [Uri]. 106 | /// 107 | /// If this encounters a connection error, [done] will complete with a 108 | /// [WebSocketChannelException]. 109 | factory VMServiceClient.connect(url) { 110 | if (url is! Uri && url is! String) { 111 | throw new ArgumentError.value(url, "url", "must be a String or a Uri"); 112 | } 113 | 114 | var uri = url is String ? Uri.parse(url) : url; 115 | if (uri.scheme == 'http') { 116 | var path = uri.path.endsWith('/') ? uri.path : uri.path + '/'; 117 | uri = uri.replace(scheme: 'ws', path: '${path}ws'); 118 | } 119 | 120 | // TODO(nweiz): Just use [WebSocketChannel.connect] when cross-platform 121 | // libraries work. 122 | return new VMServiceClient(new IOWebSocketChannel.connect(uri).cast()); 123 | } 124 | 125 | /// Creates a client that reads incoming messages from a [channel] which 126 | /// contains JSON-encoded [String] instances. 127 | /// 128 | /// This is useful when using the client over a pre-existing connection. To 129 | /// establish a connection from scratch, use [VMServiceClient.connect]. 130 | factory VMServiceClient(StreamChannel channel) => 131 | new VMServiceClient.withoutJson(jsonDocument.bind(channel)); 132 | 133 | /// Creates a client that reads incoming messages from a [channel] which 134 | /// contains decoded JSON maps and lists. 135 | /// 136 | /// This is useful when using the client over a pre-existing connection. To 137 | /// establish a connection from scratch, use [VMServiceClient.connect]. 138 | factory VMServiceClient.withoutJson(StreamChannel channel) => 139 | new VMServiceClient._(new rpc.Peer.withoutJson( 140 | channel.transformStream(v1CompatibilityTransformer))); 141 | 142 | VMServiceClient._(rpc.Peer peer) 143 | : _peer = peer, 144 | _streams = new StreamManager(peer), 145 | done = peer.listen() { 146 | _onIsolateStart = transform(_streams.isolate, (json, sink) { 147 | if (json["kind"] != "IsolateStart") return; 148 | sink.add(newVMIsolateRef(_peer, _streams, json["isolate"])); 149 | }); 150 | 151 | _onIsolateRunnable = transform(_streams.isolate, (json, sink) { 152 | if (json["kind"] != "IsolateRunnable") return; 153 | sink.add(newVMIsolateRef(_peer, _streams, json["isolate"])); 154 | }); 155 | } 156 | 157 | /// Checks the VM service protocol version and throws a 158 | /// [VMUnsupportedVersionException] if it's not a supported version. 159 | /// 160 | /// Because it's possible the VM service protocol doesn't speak JSON-RPC 2.0 161 | /// at all, by default this will also throw a [VMUnsupportedVersionException] 162 | /// if a reply isn't received within two seconds. This timeout can be 163 | /// controlled with [timeout], or `null` can be passed to use no timeout. 164 | Future validateVersion({Duration timeout: const Duration(seconds: 2)}) { 165 | var future = _peer.sendRequest("getVersion", {}).then((json) { 166 | var version; 167 | try { 168 | version = newVMServiceVersion(json); 169 | } catch (_) { 170 | throw new VMUnsupportedVersionException(); 171 | } 172 | 173 | if (version.major < 2 || version.major > 3) { 174 | throw new VMUnsupportedVersionException(version); 175 | } 176 | }); 177 | 178 | if (timeout == null) return future; 179 | 180 | return future.timeout(timeout, onTimeout: () { 181 | throw new VMUnsupportedVersionException(); 182 | }); 183 | } 184 | 185 | /// Closes the underlying connection to the VM service. 186 | /// 187 | /// Returns a [Future] that fires once the connection has been closed. 188 | Future close() => _peer.close(); 189 | 190 | /// Returns a list of flags that were passed to the VM. 191 | /// 192 | /// As of VM service version 3.0, this only includes VM-internal flags. 193 | Future> getFlags() async => 194 | newVMFlagList(await _peer.sendRequest("getFlagList", {})); 195 | 196 | /// Returns the version of the VM service protocol that this client is 197 | /// communicating with. 198 | /// 199 | /// Note that this is distinct from the version of Dart, which is accessible 200 | /// via [VM.version]. Note also that this doesn't necessarily accurately 201 | /// reflect the available APIs; for example, VM service versions 3.1, 3.2, and 202 | /// 3.3 all reported themseves as version 3.0. 203 | Future getVersion() async => 204 | newVMServiceVersion(await _peer.sendRequest("getVersion", {})); 205 | 206 | /// Returns information about the Dart VM. 207 | Future getVM() async => 208 | newVM(_peer, _streams, await _peer.sendRequest("getVM", {})); 209 | } 210 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: vm_service_client 2 | version: 0.2.6+3 3 | description: A client for the Dart VM service. 4 | author: Dart Team 5 | homepage: https://github.com/dart-lang/vm_service_client 6 | 7 | environment: 8 | sdk: ">=2.0.0 <3.0.0" 9 | 10 | dependencies: 11 | async: ^2.0.0 12 | collection: ^1.5.0 13 | json_rpc_2: ^2.0.0 14 | pub_semver: ^1.0.0 15 | source_span: ^1.4.0 16 | stack_trace: ^1.5.0 17 | stream_channel: ">=1.1.0 <3.0.0" 18 | web_socket_channel: ^1.0.0 19 | 20 | dev_dependencies: 21 | test: ^1.6.0 22 | -------------------------------------------------------------------------------- /test/bound_field_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:test/test.dart'; 6 | import 'package:vm_service_client/vm_service_client.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | VMServiceClient client; 11 | 12 | void main() { 13 | tearDown(() { 14 | if (client != null) client.close(); 15 | }); 16 | 17 | test("includes the field's declaration and value", () async { 18 | client = await runAndConnect(topLevel: r""" 19 | class Foo { 20 | final int val = 10; 21 | } 22 | """, main: r""" 23 | var foo = new Foo(); 24 | debugger(); 25 | """); 26 | 27 | var isolate = (await client.getVM()).isolates.first; 28 | await isolate.waitUntilPaused(); 29 | var stack = await isolate.getStack(); 30 | var foo = await stack.frames.first.variables["foo"].value.load(); 31 | var field = foo.fields["val"]; 32 | expect(field.declaration.name, equals("val")); 33 | expect(field.declaration.isFinal, isTrue); 34 | expect(field.value, new TypeMatcher()); 35 | expect(field.value.value, equals(10)); 36 | expect(field.toString(), equals("final int val = 10")); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /test/bound_variable_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:test/test.dart'; 6 | import 'package:vm_service_client/vm_service_client.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | VMServiceClient client; 11 | 12 | void main() { 13 | tearDown(() { 14 | if (client != null) client.close(); 15 | }); 16 | 17 | test("includes the variable's name and value", () async { 18 | client = await runAndConnect(main: r""" 19 | var foo = 'hello!'; 20 | debugger(); 21 | """); 22 | 23 | var isolate = (await client.getVM()).isolates.first; 24 | await isolate.waitUntilPaused(); 25 | var stack = await isolate.getStack(); 26 | var variable = stack.frames.first.variables["foo"]; 27 | expect(variable.name, equals('foo')); 28 | expect(variable.value, new TypeMatcher()); 29 | expect(variable.value.value, equals('hello!')); 30 | expect(variable.toString(), equals('var foo = "hello!"')); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /test/breakpoint_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:async/async.dart'; 8 | import 'package:test/test.dart'; 9 | import 'package:vm_service_client/vm_service_client.dart'; 10 | 11 | import 'utils.dart'; 12 | 13 | VMServiceClient client; 14 | 15 | void main() { 16 | tearDown(() { 17 | if (client != null) client.close(); 18 | }); 19 | 20 | test("includes the breakpoint's metadata", () async { 21 | client = await runAndConnect(main: r""" 22 | print("one"); 23 | print("two"); // line 9 24 | """, flags: ["--pause-isolates-on-start"]); 25 | 26 | var isolate = (await client.getVM()).isolates.first; 27 | 28 | var stdout = new StreamQueue(isolate.stdout.transform(lines)); 29 | var line1 = new ResultFuture(stdout.next); 30 | var line2 = new ResultFuture(stdout.next.catchError((_) {})); 31 | 32 | await isolate.waitUntilPaused(); 33 | var library = await (await isolate.loadRunnable()).rootLibrary.load(); 34 | var breakpoint = await library.scripts.single.addBreakpoint(9); 35 | expect(breakpoint.number, equals(1)); 36 | expect(breakpoint, isNot(new TypeMatcher())); 37 | expect(breakpoint.location, new TypeMatcher()); 38 | expect(breakpoint.location.uri.scheme, equals('data')); 39 | expect(breakpoint.toString(), startsWith("breakpoint #1 in data:")); 40 | 41 | await isolate.resume(); 42 | await isolate.waitUntilPaused(); 43 | 44 | // Wait long enough for the print to propagate to the future. 45 | await new Future.delayed(Duration.zero); 46 | expect(line1.result.asValue.value, equals("one")); 47 | expect(line2.result, isNull); 48 | 49 | breakpoint = await breakpoint.load(); 50 | expect(breakpoint.number, equals(1)); 51 | expect(breakpoint, new TypeMatcher()); 52 | expect(breakpoint.location, new TypeMatcher()); 53 | expect(breakpoint.location.uri.scheme, equals('data')); 54 | 55 | expect(await sourceLine(breakpoint.location), equals(8)); 56 | }); 57 | 58 | test("removes the breakpoint when remove() is called", () async { 59 | client = await runAndConnect(main: r""" 60 | print("one"); 61 | print("two"); // line 9 62 | """, flags: ["--pause-isolates-on-start"]); 63 | 64 | var isolate = (await client.getVM()).isolates.first; 65 | 66 | var stdout = new StreamQueue(isolate.stdout.transform(lines)); 67 | expect(stdout.next, completion(equals("one"))); 68 | expect(stdout.next, completion(equals("two"))); 69 | 70 | await isolate.waitUntilPaused(); 71 | var library = await (await isolate.loadRunnable()).rootLibrary.load(); 72 | var breakpoint = await library.scripts.single.addBreakpoint(9); 73 | expect(breakpoint.number, equals(1)); 74 | expect(breakpoint, isNot(new TypeMatcher())); 75 | expect(breakpoint.location, new TypeMatcher()); 76 | expect(breakpoint.location.uri.scheme, equals('data')); 77 | expect(breakpoint.toString(), startsWith("breakpoint #1 in data:")); 78 | 79 | await breakpoint.remove(); 80 | 81 | // Only a single resume event should fire. 82 | isolate.onPauseOrResume.listen(expectAsync1((event) { 83 | expect(event, new TypeMatcher()); 84 | })); 85 | 86 | await isolate.resume(); 87 | }); 88 | 89 | test("onPause fires events when the breakpoint is reached", () async { 90 | client = await runAndConnect(main: r""" 91 | print("before"); 92 | for (var i = 0; i < 3; i++) { 93 | print(i); // line 10 94 | } 95 | """, flags: ["--pause-isolates-on-start"]); 96 | 97 | var isolate = (await client.getVM()).isolates.first; 98 | 99 | var stdout = new StreamQueue(isolate.stdout 100 | .transform(lines) 101 | .transform(const SingleSubscriptionTransformer())); 102 | 103 | await isolate.waitUntilPaused(); 104 | var library = await (await isolate.loadRunnable()).rootLibrary.load(); 105 | var breakpoint = await library.scripts.single.addBreakpoint(10); 106 | expect(breakpoint, isNot(new TypeMatcher())); 107 | expect(breakpoint.location, new TypeMatcher()); 108 | 109 | var times = 0; 110 | breakpoint.onPause.listen(expectAsync1((eventBreakpoint) async { 111 | expect(eventBreakpoint.number, equals(breakpoint.number)); 112 | var i = (await isolate.getStack()).frames.first.variables['i'].value; 113 | expect(i, new TypeMatcher()); 114 | expect(i.value, equals(times)); 115 | times++; 116 | isolate.resume(); 117 | }, count: 3)); 118 | 119 | await isolate.resume(); 120 | expect(await stdout.next, equals("before")); 121 | expect(await stdout.next, equals("0")); 122 | expect(await stdout.next, equals("1")); 123 | expect(await stdout.next, equals("2")); 124 | }); 125 | 126 | test("onRemove fires once the breakpoint is removed", () async { 127 | client = await runAndConnect(main: r""" 128 | print("one"); 129 | print("two"); // line 9 130 | """, flags: ["--pause-isolates-on-start"]); 131 | 132 | var isolate = (await client.getVM()).isolates.first; 133 | 134 | await isolate.waitUntilPaused(); 135 | var library = await (await isolate.loadRunnable()).rootLibrary.load(); 136 | var breakpoint = await library.scripts.single.addBreakpoint(9); 137 | 138 | var onRemoveFuture = breakpoint.onRemove; 139 | 140 | expect((await isolate.load()).breakpoints.first.remove(), completes); 141 | 142 | await onRemoveFuture; 143 | }); 144 | 145 | test("onRemove fires if the breakpoint has already been removed", () async { 146 | client = await runAndConnect(main: r""" 147 | print("one"); 148 | print("two"); // line 9 149 | """, flags: ["--pause-isolates-on-start"]); 150 | 151 | var isolate = (await client.getVM()).isolates.first; 152 | 153 | await isolate.waitUntilPaused(); 154 | var library = await (await isolate.loadRunnable()).rootLibrary.load(); 155 | var breakpoint = await library.scripts.single.addBreakpoint(9); 156 | 157 | (await isolate.load()).breakpoints.first.remove(); 158 | 159 | await breakpoint.onRemove; 160 | }); 161 | 162 | test("loadResolved returns the breakpoint once it becomes resolved", 163 | () async { 164 | client = await runAndConnect(main: r""" 165 | print("one"); 166 | print("two"); // line 9 167 | """, flags: ["--pause-isolates-on-start"]); 168 | 169 | var isolate = (await client.getVM()).isolates.first; 170 | 171 | await isolate.waitUntilPaused(); 172 | var library = await (await isolate.loadRunnable()).rootLibrary.load(); 173 | var breakpoint = await library.scripts.single.addBreakpoint(9); 174 | expect(breakpoint, isNot(new TypeMatcher())); 175 | expect(breakpoint.location, new TypeMatcher()); 176 | 177 | var resolvedFuture = breakpoint.loadResolved(); 178 | await isolate.resume(); 179 | breakpoint = await resolvedFuture; 180 | expect(breakpoint, new TypeMatcher()); 181 | expect(breakpoint.location, new TypeMatcher()); 182 | }); 183 | 184 | test("loadResolved returns an already-resolved breakpoint", () async { 185 | client = await runAndConnect(main: r""" 186 | print("one"); 187 | print("two"); // line 9 188 | """, flags: ["--pause-isolates-on-start"]); 189 | 190 | var isolate = (await client.getVM()).isolates.first; 191 | 192 | await isolate.waitUntilPaused(); 193 | var library = await (await isolate.loadRunnable()).rootLibrary.load(); 194 | var breakpoint = await library.scripts.single.addBreakpoint(9); 195 | expect(breakpoint, isNot(new TypeMatcher())); 196 | expect(breakpoint.location, new TypeMatcher()); 197 | 198 | await isolate.resume(); 199 | await isolate.waitUntilPaused(); 200 | var resolved = await breakpoint.loadResolved(); 201 | expect(resolved, new TypeMatcher()); 202 | expect(resolved.location, new TypeMatcher()); 203 | }); 204 | } 205 | -------------------------------------------------------------------------------- /test/class_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:test/test.dart'; 6 | import 'package:vm_service_client/vm_service_client.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | VMServiceClient client; 11 | 12 | void main() { 13 | tearDown(() { 14 | if (client != null) client.close(); 15 | }); 16 | 17 | test("includes the class's metadata", () async { 18 | client = await runAndConnect(topLevel: r""" 19 | class Foo implements Comparable { 20 | final int value = 1; 21 | 22 | int compareTo(other) => 0; 23 | } 24 | 25 | class Bar extends Foo {} 26 | """, flags: ["--pause-isolates-on-start"]); 27 | 28 | var isolate = await (await client.getVM()).isolates.first.loadRunnable(); 29 | var klassRef = (await isolate.rootLibrary.load()).classes["Foo"]; 30 | 31 | expect(klassRef.name, equals("Foo")); 32 | var klass = await klassRef.load(); 33 | 34 | expect(klass.error, isNull); 35 | expect(klass.isAbstract, isFalse); 36 | expect(klass.isConst, isFalse); 37 | expect(klass.library.uri.scheme, equals("data")); 38 | expect(klass.location.script.uri.scheme, equals("data")); 39 | expect(klass.superclass.name, equals("Object")); 40 | expect(klass.interfaces.single.name, equals("Comparable")); 41 | expect(klass.fields, contains("value")); 42 | expect(klass.functions, contains("compareTo")); 43 | expect(klass.subclasses.single.name, equals("Bar")); 44 | }); 45 | 46 | test("evaluate() evaluates code in the context of the class", () async { 47 | client = await runAndConnect(topLevel: r""" 48 | class Foo { 49 | static int foo(int value) => value + 12; 50 | } 51 | """, flags: ["--pause-isolates-on-start"]); 52 | 53 | var isolate = await (await client.getVM()).isolates.first.loadRunnable(); 54 | var klass = (await isolate.rootLibrary.load()).classes["Foo"]; 55 | var value = await klass.evaluate("foo(6)") as VMIntInstanceRef; 56 | expect(value.value, equals(18)); 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /test/client_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:test/test.dart'; 8 | import 'package:vm_service_client/vm_service_client.dart'; 9 | import 'package:web_socket_channel/web_socket_channel.dart'; 10 | 11 | import 'utils.dart'; 12 | 13 | VMServiceClient client; 14 | 15 | void main() { 16 | tearDown(() { 17 | if (client != null) client.close(); 18 | }); 19 | 20 | test("returns the VM service version", () async { 21 | client = await runAndConnect(); 22 | var version = await client.getVersion(); 23 | expect(version.major, equals(3)); 24 | expect(version.minor, new TypeMatcher()); 25 | }); 26 | 27 | test("considers the VM service version valid", () async { 28 | client = await runAndConnect(); 29 | await client.validateVersion(); 30 | }); 31 | 32 | test("validateVersion() respects a custom timeout", () async { 33 | client = await runAndConnect(); 34 | expect(client.validateVersion(timeout: Duration.zero), 35 | throwsA(new TypeMatcher())); 36 | }); 37 | 38 | test("returns the flags passed to the VM", () async { 39 | client = await runAndConnect(); 40 | 41 | // TODO(nweiz): check flags we pass and verify VMFlag.modified when 42 | // sdk#24143 is fixed. 43 | var flags = await client.getFlags(); 44 | var flag = 45 | flags.firstWhere((flag) => flag.name == "optimization_counter_scale"); 46 | expect(flag.value, equals("2000")); 47 | }); 48 | 49 | test("onIsolateStart emits an event when an isolate starts", () async { 50 | client = await runAndConnect(topLevel: r""" 51 | void inIsolate(_) { 52 | new ReceivePort(); 53 | } 54 | """, main: r""" 55 | Isolate.spawn(inIsolate, null); 56 | """, flags: ["--pause-isolates-on-start"]); 57 | 58 | scheduleMicrotask(() async { 59 | (await client.getVM()).isolates.last.resume(); 60 | }); 61 | 62 | var isolate = await (await client.onIsolateStart.first).load(); 63 | // If [isolate] is runnable by the time this fires, this will be a 64 | // VMPauseStartEvent. If it's not runnable yet, it'll be a VMNoneEvent. 65 | expect( 66 | isolate.pauseEvent, 67 | anyOf([ 68 | new TypeMatcher(), 69 | new TypeMatcher() 70 | ])); 71 | expect(isolate.error, isNull); 72 | 73 | isolate = await isolate.loadRunnable(); 74 | expect(isolate.pauseEvent, new TypeMatcher()); 75 | expect(isolate.error, isNull); 76 | }); 77 | 78 | test("with an invalid URL", () { 79 | var client = new VMServiceClient.connect("ws://example.org/not-a-ws"); 80 | expect(client.done, throwsA(new TypeMatcher())); 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /test/code_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:test/test.dart'; 6 | import 'package:vm_service_client/vm_service_client.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | VMServiceClient client; 11 | 12 | void main() { 13 | tearDown(() { 14 | if (client != null) client.close(); 15 | }); 16 | 17 | test("includes the code's metadata", () async { 18 | client = await runAndConnect(topLevel: r""" 19 | void foo() { 20 | print("hello!"); 21 | } 22 | """, flags: ["--pause-isolates-on-start"]); 23 | 24 | var isolate = await (await client.getVM()).isolates.first.loadRunnable(); 25 | var function = (await isolate.rootLibrary.load()).functions["foo"]; 26 | var code = (await function.load()).code; 27 | 28 | expect(code.name, equals("[Stub] LazyCompile")); 29 | expect(code.kind, equals(VMCodeKind.stub)); 30 | expect(code.toString(), equals("[Stub] LazyCompile")); 31 | 32 | await (function.owner as VMLibraryRef).evaluate("foo()"); 33 | code = (await function.load()).code; 34 | expect(code.name, equals("foo")); 35 | expect(code.kind, equals(VMCodeKind.dart)); 36 | expect(code.toString(), equals("foo (Dart)")); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /test/error_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:test/test.dart'; 6 | import 'package:vm_service_client/vm_service_client.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | VMServiceClient client; 11 | 12 | void main() { 13 | tearDown(() { 14 | if (client != null) client.close(); 15 | }); 16 | 17 | group("for an async error", () { 18 | var error; 19 | setUp(() async { 20 | client = await runAndConnect(main: r""" 21 | throw "oh no"; 22 | """, flags: ["--pause-isolates-on-exit"]); 23 | 24 | var isolate = await (await client.getVM()).isolates.first.load(); 25 | await isolate.waitUntilPaused(); 26 | error = (await isolate.load()).error; 27 | }); 28 | 29 | test("includes error metadata", () async { 30 | expect(error.kind, equals(VMErrorKind.unhandledException)); 31 | expect(error.message, startsWith("Unhandled exception:\noh no")); 32 | 33 | expect(error.exception, new TypeMatcher()); 34 | expect(error.exception.value, equals("oh no")); 35 | }); 36 | 37 | test("parses the stack trace", () async { 38 | var trace = await error.getTrace(); 39 | expect(trace.frames.first.member, equals('main')); 40 | }); 41 | }); 42 | 43 | group("for a sync error", () { 44 | var error; 45 | setUp(() async { 46 | client = await runAndConnect(main: r""" 47 | throw "oh no"; 48 | """, flags: ["--pause-isolates-on-exit"], sync: true); 49 | 50 | var isolate = await (await client.getVM()).isolates.first.load(); 51 | await isolate.waitUntilPaused(); 52 | error = (await isolate.load()).error; 53 | }); 54 | 55 | test("includes error metadata", () async { 56 | expect(error.kind, equals(VMErrorKind.unhandledException)); 57 | expect(error.message, startsWith("Unhandled exception:\noh no")); 58 | 59 | expect(error.exception, new TypeMatcher()); 60 | expect(error.exception.value, equals("oh no")); 61 | }); 62 | 63 | test("parses the stack trace", () async { 64 | var trace = await error.getTrace(); 65 | expect(trace.frames.first.member, equals('main')); 66 | }); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /test/exception_handling_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:test/test.dart'; 6 | import 'package:vm_service_client/vm_service_client.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | VMServiceClient client; 11 | 12 | void main() { 13 | tearDown(() { 14 | if (client != null) client.close(); 15 | }); 16 | 17 | test("does not pause on exceptions by default", () async { 18 | client = await runAndConnect(main: r""" 19 | throw 'err'; 20 | 21 | print('Done!'); 22 | """, flags: ["--pause-isolates-on-start"]); 23 | final isolate = (await client.getVM()).isolates.first; 24 | 25 | // Pauses-on-start. 26 | await isolate.waitUntilPaused(); 27 | 28 | await isolate.resume(); 29 | await isolate.waitUntilPaused(); 30 | expect((await isolate.load()).pauseEvent, isA()); 31 | }); 32 | 33 | test("unhandled pauses only on unhandled exceptions", () async { 34 | client = await runAndConnect(main: r""" 35 | try { 36 | throw 'err2'; // line 8 37 | } catch (e) { 38 | } 39 | 40 | throw 'err'; // line 12 41 | 42 | print('Done!'); 43 | """, flags: ["--pause-isolates-on-start"]); 44 | 45 | var isolate = (await client.getVM()).isolates.first; 46 | 47 | // Pauses-on-start. 48 | await isolate.waitUntilPaused(); 49 | await isolate.setExceptionPauseMode(VMExceptionPauseMode.unhandled); 50 | 51 | // Except pause on the second throw. 52 | await isolate.resume(); 53 | await isolate.waitUntilPaused(); 54 | final frame = (await isolate.getStack()).frames.first; 55 | expect(await sourceLine(frame.location), equals(12)); 56 | 57 | // Resume and expect termination. 58 | await isolate.resume(); 59 | await isolate.waitUntilPaused(); 60 | expect((await isolate.load()).pauseEvent, isA()); 61 | }); 62 | 63 | test("all pauses only on all exceptions", () async { 64 | client = await runAndConnect(main: r""" 65 | try { 66 | throw 'err2'; // line 8 67 | } catch (e) { 68 | } 69 | 70 | throw 'err'; // line 12 71 | 72 | print('Done!'); 73 | """, flags: ["--pause-isolates-on-start"]); 74 | 75 | var isolate = (await client.getVM()).isolates.first; 76 | 77 | // Pauses-on-start. 78 | await isolate.waitUntilPaused(); 79 | await isolate.setExceptionPauseMode(VMExceptionPauseMode.all); 80 | 81 | // Except pause on the first throw. 82 | await isolate.resume(); 83 | await isolate.waitUntilPaused(); 84 | var frame = (await isolate.getStack()).frames.first; 85 | expect(await sourceLine(frame.location), equals(8)); 86 | 87 | // Except pause on the second throw. 88 | await isolate.resume(); 89 | await isolate.waitUntilPaused(); 90 | frame = (await isolate.getStack()).frames.first; 91 | expect(await sourceLine(frame.location), equals(12)); 92 | 93 | // Resume and expect termination. 94 | await isolate.resume(); 95 | await isolate.waitUntilPaused(); 96 | expect((await isolate.load()).pauseEvent, isA()); 97 | }); 98 | 99 | test("exception mode can be read and set", () async { 100 | client = await runAndConnect(flags: ["--pause-isolates-on-start"]); 101 | 102 | var isolate = (await client.getVM()).isolates.first; 103 | 104 | // Pauses-on-start. 105 | await isolate.waitUntilPaused(); 106 | 107 | expect((await isolate.load()).exceptionPauseMode.toString(), 108 | equals(VMExceptionPauseMode.none.toString())); 109 | 110 | await isolate.setExceptionPauseMode(VMExceptionPauseMode.unhandled); 111 | expect((await isolate.load()).exceptionPauseMode.toString(), 112 | equals(VMExceptionPauseMode.unhandled.toString())); 113 | 114 | await isolate.setExceptionPauseMode(VMExceptionPauseMode.all); 115 | expect((await isolate.load()).exceptionPauseMode.toString(), 116 | equals(VMExceptionPauseMode.all.toString())); 117 | 118 | await isolate.setExceptionPauseMode(VMExceptionPauseMode.none); 119 | expect((await isolate.load()).exceptionPauseMode.toString(), 120 | equals(VMExceptionPauseMode.none.toString())); 121 | }); 122 | } 123 | -------------------------------------------------------------------------------- /test/field_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:test/test.dart'; 6 | import 'package:vm_service_client/vm_service_client.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | VMServiceClient client; 11 | 12 | void main() { 13 | tearDown(() { 14 | if (client != null) client.close(); 15 | }); 16 | 17 | test("includes a top-level field's metadata", () async { 18 | client = await runAndConnect(topLevel: r""" 19 | const String value = 'foo'; 20 | """, flags: ["--pause-isolates-on-start"]); 21 | 22 | var isolate = await (await client.getVM()).isolates.first.loadRunnable(); 23 | var fieldRef = (await isolate.rootLibrary.load()).fields["value"]; 24 | 25 | expect(fieldRef.name, equals("value")); 26 | expect( 27 | (fieldRef.owner as VMLibraryRef).uri, equals(isolate.rootLibrary.uri)); 28 | expect((fieldRef.declaredType as VMTypeInstanceRef).name, equals("String")); 29 | expect(fieldRef.isConst, isTrue); 30 | expect(fieldRef.isFinal, isTrue); 31 | expect(fieldRef.isStatic, isTrue); 32 | expect(fieldRef.description, equals("const String value")); 33 | expect(fieldRef.toString(), equals("const String value = ...")); 34 | 35 | var field = await fieldRef.load(); 36 | expect((field.value as VMStringInstanceRef).value, equals("foo")); 37 | expect(field.location.script.uri, equals(isolate.rootLibrary.uri)); 38 | expect(field.toString(), equals('const String value = "foo"')); 39 | }); 40 | 41 | test("includes an instance field's metadata", () async { 42 | client = await runAndConnect(topLevel: r""" 43 | class Foo { 44 | var value = 12; 45 | } 46 | """, flags: ["--pause-isolates-on-start"]); 47 | 48 | var isolate = await (await client.getVM()).isolates.first.loadRunnable(); 49 | var klass = (await isolate.rootLibrary.load()).classes["Foo"]; 50 | var instance = await (await klass.evaluate("new Foo()")).load(); 51 | var fieldRef = instance.fields["value"].declaration; 52 | 53 | expect(fieldRef.name, equals("value")); 54 | expect((fieldRef.owner as VMClassRef).name, equals("Foo")); 55 | expect( 56 | (fieldRef.declaredType as VMTypeInstanceRef).name, equals("dynamic")); 57 | expect(fieldRef.isConst, isFalse); 58 | expect(fieldRef.isFinal, isFalse); 59 | expect(fieldRef.isStatic, isFalse); 60 | expect(fieldRef.description, equals("var value")); 61 | expect(fieldRef.toString(), equals("var value = ...")); 62 | 63 | var field = await fieldRef.load(); 64 | expect(field.value, isNull); 65 | expect(field.location.script.uri, equals(isolate.rootLibrary.uri)); 66 | expect(field.toString(), equals('var value = ...')); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /test/frame_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:vm_service_client/vm_service_client.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | VMServiceClient client; 11 | 12 | void main() { 13 | tearDown(() { 14 | if (client != null) client.close(); 15 | }); 16 | 17 | test("includes a frame's metadata", () async { 18 | client = await runAndConnect(main: r""" 19 | var foo = 'hello!'; 20 | debugger(); 21 | """); 22 | 23 | var isolate = (await client.getVM()).isolates.first; 24 | await isolate.waitUntilPaused(); 25 | var frame = (await isolate.getStack()).frames.first; 26 | expect(frame.index, equals(0)); 27 | expect(frame.function.name, equals("")); 28 | expect(frame.code.kind, equals(VMCodeKind.dart)); 29 | expect(frame.variables, contains("foo")); 30 | expect(frame.toString(), equals("#0 in ")); 31 | expect(await sourceLine(frame.location), equals(10)); 32 | }); 33 | 34 | test("evaluate() evaluates code in the context of the frame", () async { 35 | client = await runAndConnect(main: r""" 36 | var foo = 'hello!'; 37 | debugger(); 38 | """); 39 | 40 | var isolate = (await client.getVM()).isolates.first; 41 | await isolate.waitUntilPaused(); 42 | var frame = (await isolate.getStack()).frames.first; 43 | var value = await frame.evaluate("foo + ' world'") as VMStringInstanceRef; 44 | expect(value.value, equals("hello! world")); 45 | }); 46 | 47 | test("getFrame() returns a stack_trace frame", () async { 48 | var client = await runAndConnect(main: r""" 49 | debugger(); 50 | """); 51 | 52 | var isolate = (await client.getVM()).isolates.first; 53 | await isolate.waitUntilPaused(); 54 | var frame = await (await isolate.getStack()).frames.first.getFrame(); 55 | expect(frame.uri.scheme, equals('data')); 56 | expect(frame.line, equals(9)); 57 | expect(frame.column, equals(0)); 58 | expect(frame.member, equals('main.')); 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /test/function_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:test/test.dart'; 6 | import 'package:vm_service_client/vm_service_client.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | VMServiceClient client; 11 | 12 | void main() { 13 | tearDown(() { 14 | if (client != null) client.close(); 15 | }); 16 | 17 | group("includes metadata for", () { 18 | test("a top-level function metadata", () async { 19 | client = await runAndConnect(topLevel: r""" 20 | void foo(int arg) {} 21 | """, flags: ["--pause-isolates-on-start"]); 22 | 23 | var isolate = await (await client.getVM()).isolates.first.loadRunnable(); 24 | var functionRef = (await isolate.rootLibrary.load()).functions["foo"]; 25 | 26 | expect(functionRef.name, equals("foo")); 27 | expect((functionRef.owner as VMLibraryRef).uri.scheme, equals("data")); 28 | expect(functionRef.isStatic, isTrue); 29 | expect(functionRef.isConst, isFalse); 30 | expect(functionRef.toString(), equals("foo")); 31 | 32 | var function = await functionRef.load(); 33 | expect(function.code.kind, equals(VMCodeKind.stub)); 34 | expect(await sourceLine(function.location), equals(2)); 35 | }); 36 | 37 | test("a static function", () async { 38 | client = await runAndConnect(topLevel: r""" 39 | class Foo { 40 | static void foo(int arg) {} 41 | } 42 | """, flags: ["--pause-isolates-on-start"]); 43 | 44 | var isolate = await (await client.getVM()).isolates.first.loadRunnable(); 45 | var klass = (await isolate.rootLibrary.load()).classes["Foo"]; 46 | var functionRef = (await klass.load()).functions["foo"]; 47 | 48 | expect(functionRef.name, equals("foo")); 49 | expect((functionRef.owner as VMClassRef).name, equals("Foo")); 50 | expect(functionRef.isStatic, isTrue); 51 | expect(functionRef.isConst, isFalse); 52 | expect(functionRef.toString(), equals("static foo")); 53 | 54 | var function = await functionRef.load(); 55 | expect(function.code.kind, equals(VMCodeKind.stub)); 56 | expect(await sourceLine(function.location), equals(3)); 57 | }); 58 | 59 | test("an instance function", () async { 60 | client = await runAndConnect(topLevel: r""" 61 | class Foo { 62 | void foo(int arg) {} 63 | } 64 | """, flags: ["--pause-isolates-on-start"]); 65 | 66 | var isolate = await (await client.getVM()).isolates.first.loadRunnable(); 67 | var klass = (await isolate.rootLibrary.load()).classes["Foo"]; 68 | var functionRef = (await klass.load()).functions["foo"]; 69 | 70 | expect(functionRef.name, equals("foo")); 71 | expect((functionRef.owner as VMClassRef).name, equals("Foo")); 72 | expect(functionRef.isStatic, isFalse); 73 | expect(functionRef.isConst, isFalse); 74 | expect(functionRef.toString(), equals("foo")); 75 | 76 | var function = await functionRef.load(); 77 | expect(function.code.kind, equals(VMCodeKind.stub)); 78 | expect(await sourceLine(function.location), equals(3)); 79 | }); 80 | }); 81 | 82 | test("addBreakpoint() adds a breakpoint before the function", () async { 83 | client = await runAndConnect(topLevel: r""" 84 | void foo(int arg) { 85 | print(arg); 86 | } 87 | """, flags: ["--pause-isolates-on-start"]); 88 | 89 | var isolate = await (await client.getVM()).isolates.first.loadRunnable(); 90 | var function = (await isolate.rootLibrary.load()).functions["foo"]; 91 | 92 | await function.addBreakpoint(); 93 | isolate.rootLibrary.evaluate("foo(12)"); 94 | await isolate.waitUntilPaused(); 95 | 96 | var frame = (await isolate.getStack()).frames.first; 97 | expect(frame.function.name, equals("foo")); 98 | }); 99 | } 100 | -------------------------------------------------------------------------------- /test/instance_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:typed_data'; 7 | 8 | import 'package:vm_service_client/vm_service_client.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | import 'utils.dart'; 12 | 13 | VMServiceClient client; 14 | 15 | void main() { 16 | group("for a plain instance", () { 17 | var value; 18 | setUp(() async { 19 | client = await runAndConnect(topLevel: r""" 20 | class Foo { 21 | final int _value; 22 | 23 | Foo(this._value); 24 | } 25 | """, flags: ["--pause-isolates-on-start"]); 26 | value = await _evaluate("new Foo(12)"); 27 | }); 28 | 29 | tearDown(() => client.close()); 30 | 31 | test("includes the instance's metadata", () async { 32 | expect(value.klass.name, equals("Foo")); 33 | expect(value.toString(), equals("Remote instance of 'Foo'")); 34 | expect((await value.load()).fields, contains("_value")); 35 | }); 36 | 37 | test("evaluate() runs in the context of the instance", () async { 38 | var result = await value.evaluate("this._value + 1") as VMIntInstanceRef; 39 | expect(result.value, equals(13)); 40 | }); 41 | 42 | test("getValue() runs onUnknownValue", () async { 43 | var result = 44 | await value.getValue(onUnknownValue: expectAsync1((innerValue) { 45 | expect(innerValue, same(value)); 46 | return 123; 47 | })); 48 | expect(result, equals(123)); 49 | }); 50 | }); 51 | 52 | group("for a client with no code:", () { 53 | setUp(() async { 54 | client = await runAndConnect(flags: ["--pause-isolates-on-start"]); 55 | }); 56 | 57 | tearDown(() => client.close()); 58 | 59 | test("a null", () async { 60 | var value = await _evaluate("null") as VMNullInstanceRef; 61 | 62 | expect(value.value, isNull); 63 | expect((await value.load()).value, isNull); 64 | expect(await value.getValue(onUnknownValue: neverCalled), isNull); 65 | }); 66 | 67 | test("a bool", () async { 68 | var value = await _evaluate("true") as VMBoolInstanceRef; 69 | 70 | expect(value.value, isTrue); 71 | expect((await value.load()).value, isTrue); 72 | expect(await value.getValue(onUnknownValue: neverCalled), isTrue); 73 | }); 74 | 75 | test("a double", () async { 76 | var value = await _evaluate("12.3") as VMDoubleInstanceRef; 77 | 78 | expect(value.value, equals(12.3)); 79 | expect((await value.load()).value, equals(12.3)); 80 | expect(await value.getValue(onUnknownValue: neverCalled), equals(12.3)); 81 | }); 82 | 83 | test("an int", () async { 84 | var value = await _evaluate("12") as VMIntInstanceRef; 85 | 86 | expect(value.value, equals(12)); 87 | expect((await value.load()).value, equals(12)); 88 | expect(await value.getValue(onUnknownValue: neverCalled), equals(12)); 89 | }); 90 | 91 | test("a Float32x4", () async { 92 | var value = await _evaluate("new Float32x4(1.23, 2.34, 3.45, 4.56)") 93 | as VMFloat32x4InstanceRef; 94 | 95 | expect(value.value.x, closeTo(1.23, 0.00001)); 96 | expect(value.value.y, closeTo(2.34, 0.00001)); 97 | expect(value.value.z, closeTo(3.45, 0.00001)); 98 | expect(value.value.w, closeTo(4.56, 0.00001)); 99 | expect((await value.load()).toString(), equals(value.value.toString())); 100 | expect((await value.getValue(onUnknownValue: neverCalled)).toString(), 101 | equals(value.value.toString())); 102 | }); 103 | 104 | test("a Float64x2", () async { 105 | var value = await _evaluate("new Float64x2(1.23, 2.34)") 106 | as VMFloat64x2InstanceRef; 107 | 108 | expect(value.value.x, closeTo(1.23, 0.00001)); 109 | expect(value.value.y, closeTo(2.34, 0.00001)); 110 | expect((await value.load()).toString(), equals(value.value.toString())); 111 | expect((await value.getValue(onUnknownValue: neverCalled)).toString(), 112 | equals(value.value.toString())); 113 | }); 114 | 115 | test("an Int32x4", () async { 116 | var value = await _evaluate("new Int32x4(123, 234, 345, 456)") 117 | as VMInt32x4InstanceRef; 118 | 119 | expect(value.value.x, equals(123)); 120 | expect(value.value.y, equals(234)); 121 | expect(value.value.z, equals(345)); 122 | expect(value.value.w, equals(456)); 123 | expect((await value.load()).toString(), equals(value.value.toString())); 124 | expect((await value.getValue(onUnknownValue: neverCalled)).toString(), 125 | equals(value.value.toString())); 126 | }); 127 | 128 | test("a StackTrace", () async { 129 | var value = await _evaluate(r""" 130 | (() { 131 | try { 132 | throw 'oh no!'; 133 | } catch (error, stackTrace) { 134 | return stackTrace; 135 | } 136 | })() 137 | """) as VMStackTraceInstanceRef; 138 | 139 | expect(value.value.frames.first.uri.scheme, equals("evaluate")); 140 | expect(await value.getValue(onUnknownValue: neverCalled), 141 | equals(value.value)); 142 | expect((await value.load()).value.toString(), 143 | equals(value.value.toString())); 144 | }); 145 | 146 | test("a short String", () async { 147 | var value = await _evaluate(r"""'f\'\"oo'""") as VMStringInstanceRef; 148 | 149 | expect(value.value, equals("f'\"oo")); 150 | expect(value.isValueTruncated, isFalse); 151 | expect(value.toString(), equals('"f\'\\"oo"')); 152 | expect( 153 | await value.getValue(onUnknownValue: neverCalled), equals("f'\"oo")); 154 | expect((await value.load()).value, equals("f'\"oo")); 155 | }); 156 | 157 | test("a long String", () async { 158 | var value = await _evaluate(r"""'foo' * 10000""") as VMStringInstanceRef; 159 | 160 | expect(value.value, startsWith("foo")); 161 | expect(value.isValueTruncated, isTrue); 162 | expect(value.toString(), endsWith('..."')); 163 | expect(await value.getValue(onUnknownValue: neverCalled), 164 | equals("foo" * 10000)); 165 | 166 | value = await value.load(); 167 | expect(value.value, equals("foo" * 10000)); 168 | expect(value.isValueTruncated, isFalse); 169 | expect(value.toString(), equals('"${"foo" * 10000}"')); 170 | }); 171 | 172 | test("a List", () async { 173 | var valueRef = await _evaluate("[1, 2, 3, 4]") as VMListInstanceRef; 174 | 175 | expect(valueRef.length, equals(4)); 176 | expect(valueRef.toString(), equals("[...]")); 177 | expect(await valueRef.getValue(), equals([1, 2, 3, 4])); 178 | 179 | var value = await valueRef.load(); 180 | expect( 181 | value.elements, 182 | allOf([ 183 | hasLength(4), 184 | everyElement(new TypeMatcher()) 185 | ])); 186 | expect(value.toString(), equals("[1, 2, 3, 4]")); 187 | expect(await value.getValue(), equals([1, 2, 3, 4])); 188 | }); 189 | 190 | test("a List containing an unconvertable instance", () async { 191 | var value = await _evaluate("[() {}]"); 192 | 193 | expect(value.getValue(), throwsUnsupportedError); 194 | expect( 195 | await value.getValue(onUnknownValue: expectAsync1((value) { 196 | expect(value, new TypeMatcher()); 197 | return null; 198 | })), 199 | equals([null])); 200 | }); 201 | 202 | test("a Map", () async { 203 | var valueRef = await _evaluate("{1: 2, 3: 4}") as VMMapInstanceRef; 204 | 205 | expect(valueRef.length, equals(2)); 206 | expect(valueRef.toString(), equals("{...}")); 207 | expect(await valueRef.getValue(), equals({1: 2, 3: 4})); 208 | 209 | var value = await valueRef.load(); 210 | expect(value.associations, hasLength(2)); 211 | expect(value.associations.first.key, new TypeMatcher()); 212 | expect( 213 | value.associations.first.value, new TypeMatcher()); 214 | expect(value.associations.last.key, new TypeMatcher()); 215 | expect( 216 | value.associations.last.value, new TypeMatcher()); 217 | expect(value.toString(), equals("{1: 2, 3: 4}")); 218 | expect(await value.getValue(), equals({1: 2, 3: 4})); 219 | }); 220 | 221 | test("a Map containing an unconvertable instance", () async { 222 | var value = await _evaluate("{1: () {}}"); 223 | 224 | expect(value.getValue(), throwsUnsupportedError); 225 | expect( 226 | await value.getValue(onUnknownValue: expectAsync1((value) { 227 | expect(value, new TypeMatcher()); 228 | return null; 229 | })), 230 | equals({1: null})); 231 | }); 232 | 233 | test("a TypedData", () async { 234 | var valueRef = await _evaluate("new Uint8List.fromList([1, 2, 3, 4])") 235 | as VMTypedDataInstanceRef; 236 | 237 | expect(valueRef.length, equals(4)); 238 | expect(valueRef.toString(), equals("[...]")); 239 | expect( 240 | await valueRef.getValue(), 241 | allOf([ 242 | new TypeMatcher(), 243 | equals([1, 2, 3, 4]) 244 | ])); 245 | 246 | var value = await valueRef.load(); 247 | expect( 248 | value.value, 249 | allOf([ 250 | new TypeMatcher(), 251 | equals([1, 2, 3, 4]) 252 | ])); 253 | expect(value.toString(), equals("[1, 2, 3, 4]")); 254 | expect( 255 | await value.getValue(), 256 | allOf([ 257 | new TypeMatcher(), 258 | equals([1, 2, 3, 4]) 259 | ])); 260 | }); 261 | 262 | test("a RegExp", () async { 263 | var valueRef = await _evaluate("new RegExp('foo', caseSensitive: false)") 264 | as VMRegExpInstanceRef; 265 | 266 | expect(valueRef.pattern.isValueTruncated, isFalse); 267 | expect(valueRef.pattern.value, equals("foo")); 268 | expect(valueRef.toString(), equals('"foo"')); 269 | expect(await valueRef.getValue(), 270 | equals(new RegExp('foo', caseSensitive: false))); 271 | 272 | var value = await valueRef.load(); 273 | expect(value.pattern.isValueTruncated, isFalse); 274 | expect(value.pattern.value, equals("foo")); 275 | expect(value.isCaseSensitive, isFalse); 276 | expect(value.isMultiLine, isFalse); 277 | expect(await value.getValue(), 278 | equals(new RegExp('foo', caseSensitive: false))); 279 | }); 280 | 281 | test("a RegExp with a long pattern", () async { 282 | var valueRef = 283 | await _evaluate("new RegExp('foo' * 10000, multiLine: true)") 284 | as VMRegExpInstanceRef; 285 | 286 | expect(valueRef.pattern.isValueTruncated, isTrue); 287 | expect(valueRef.pattern.value, startsWith("foo")); 288 | expect(valueRef.toString(), endsWith('..."')); 289 | expect(await valueRef.getValue(), 290 | equals(new RegExp('foo' * 10000, multiLine: true))); 291 | 292 | var value = await valueRef.load(); 293 | expect(value.pattern.isValueTruncated, isTrue); 294 | expect(value.pattern.value, startsWith("foo")); 295 | expect(value.isCaseSensitive, isTrue); 296 | expect(value.isMultiLine, isTrue); 297 | expect(await value.getValue(), 298 | equals(new RegExp('foo' * 10000, multiLine: true))); 299 | }); 300 | 301 | test("a function", () async { 302 | var valueRef = await _evaluate(""" 303 | (() { 304 | var i = 0; 305 | myFunction() => i; 306 | return myFunction; 307 | })() 308 | """) as VMClosureInstanceRef; 309 | 310 | var value = await valueRef.load(); 311 | expect(value.function.name, equals("myFunction")); 312 | expect(value.context.length, equals(1)); 313 | 314 | var context = await value.context.load(); 315 | expect(context.variables, hasLength(1)); 316 | expect(context.variables.first, new TypeMatcher()); 317 | expect(context.parent, isNull); 318 | }); 319 | 320 | test("a type", () async { 321 | var valueRef = 322 | await _evaluate("[].runtimeType") as VMTypeInstanceRef; 323 | 324 | expect(valueRef.name, equals("List")); 325 | expect(valueRef.typeClass.name, equals("_GrowableList")); 326 | 327 | var value = await valueRef.load(); 328 | var argumentsRef = value.arguments; 329 | expect(argumentsRef.name, equals("")); 330 | 331 | var arguments = await argumentsRef.load(); 332 | expect(arguments.types, hasLength(1)); 333 | var argument = arguments.types.first as VMTypeInstanceRef; 334 | expect(argument.name, equals("int")); 335 | }); 336 | }); 337 | } 338 | 339 | Future _evaluate(String expression) async { 340 | var isolate = await (await client.getVM()).isolates.first.loadRunnable(); 341 | return await isolate.rootLibrary.evaluate(expression); 342 | } 343 | -------------------------------------------------------------------------------- /test/library_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:test/test.dart'; 6 | import 'package:vm_service_client/vm_service_client.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | VMServiceClient client; 11 | 12 | void main() { 13 | tearDown(() { 14 | if (client != null) client.close(); 15 | }); 16 | 17 | test("includes the library's metadata", () async { 18 | client = await runAndConnect(topLevel: r""" 19 | import 'dart:convert' as convert; 20 | export 'dart:typed_data'; 21 | 22 | final foo = 1; 23 | 24 | bar() {} 25 | 26 | class Baz {} 27 | """, flags: ["--pause-isolates-on-start"]); 28 | 29 | var isolate = await (await client.getVM()).isolates.first.loadRunnable(); 30 | var libraryRef = isolate.rootLibrary; 31 | 32 | expect(libraryRef.uri.scheme, equals("data")); 33 | var library = await libraryRef.load(); 34 | 35 | expect(library.isDebuggable, isTrue); 36 | 37 | expect( 38 | library.dependencies, 39 | contains(predicate((dependency) { 40 | return dependency.isImport && 41 | dependency.prefix == 'convert' && 42 | dependency.target.uri.toString() == 'dart:convert'; 43 | }, "import 'dart:convert' as convert"))); 44 | 45 | expect( 46 | library.dependencies, 47 | contains(predicate((dependency) { 48 | return !dependency.isImport && 49 | dependency.prefix == null && 50 | dependency.target.uri.toString() == 'dart:typed_data'; 51 | }, "export 'dart:typed_data'"))); 52 | 53 | expect(library.scripts, hasLength(1)); 54 | expect(library.scripts.single.uri, equals(library.uri)); 55 | expect(library.fields, contains("foo")); 56 | expect(library.functions, contains("bar")); 57 | expect(library.classes, contains("Baz")); 58 | }); 59 | 60 | test("setNotDebuggable and setDebuggable control library debuggability", 61 | () async { 62 | client = await runAndConnect(main: """ 63 | print('here'); // line 8 64 | """, flags: ["--pause-isolates-on-start"]); 65 | 66 | var isolate = await (await client.getVM()).isolates.first.loadRunnable(); 67 | var library = await isolate.rootLibrary.load(); 68 | 69 | await library.setNotDebuggable(); 70 | expect((await library.load()).isDebuggable, isFalse); 71 | 72 | await library.setDebuggable(); 73 | expect((await library.load()).isDebuggable, isTrue); 74 | }); 75 | 76 | test("evaluate() evaluates code in the context of the library", () async { 77 | var client = await runAndConnect(topLevel: r""" 78 | int foo(int value) => value + 12; 79 | """, flags: ["--pause-isolates-on-start"]); 80 | 81 | var isolate = await (await client.getVM()).isolates.first.loadRunnable(); 82 | var value = 83 | await isolate.rootLibrary.evaluate("foo(6)") as VMIntInstanceRef; 84 | expect(value.value, equals(18)); 85 | }); 86 | } 87 | -------------------------------------------------------------------------------- /test/message_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:test/test.dart'; 6 | import 'package:vm_service_client/vm_service_client.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | VMServiceClient client; 11 | 12 | void main() { 13 | tearDown(() { 14 | if (client != null) client.close(); 15 | }); 16 | 17 | test("includes the message's metadata", () async { 18 | client = await runAndConnect(main: r""" 19 | var port = new ReceivePort(); 20 | port.sendPort.send("hi!"); 21 | debugger(); 22 | """); 23 | 24 | var isolate = await (await client.getVM()).isolates.first.load(); 25 | await isolate.waitUntilPaused(); 26 | 27 | var stack = await isolate.getStack(); 28 | expect(stack.messages, hasLength(1)); 29 | var message = stack.messages.single; 30 | 31 | expect(message.index, equals(0)); 32 | expect(message.name, isNotNull); 33 | expect(message.size, greaterThan(0)); 34 | expect(message.handler, isNotNull); 35 | expect(message.location, isNotNull); 36 | 37 | var value = await message.loadValue() as VMStringInstanceRef; 38 | expect(value.value, equals("hi!")); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /test/script_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:source_span/source_span.dart'; 6 | import 'package:test/test.dart'; 7 | import 'package:vm_service_client/vm_service_client.dart'; 8 | 9 | import 'utils.dart'; 10 | 11 | void main() { 12 | VMServiceClient client; 13 | VMRunnableIsolate isolate; 14 | VMScriptRef scriptRef; 15 | VMScript script; 16 | VMField foo; 17 | VMFunction bar; 18 | VMFunction baz; 19 | 20 | setUp(() async { 21 | client = await runAndConnect(topLevel: r""" 22 | final foo = 1; // line 2 23 | 24 | int bar() => 1; // line 4 25 | 26 | int baz() { // line 6 27 | return 1; 28 | } 29 | """, flags: ["--pause-isolates-on-start"]); 30 | isolate = await (await client.getVM()).isolates.first.load(); 31 | scriptRef = (await isolate.rootLibrary.load()).scripts.single; 32 | 33 | script = await scriptRef.load(); 34 | var library = await isolate.rootLibrary.load(); 35 | foo = await library.fields["foo"].load(); 36 | bar = await library.functions["bar"].load(); 37 | baz = await library.functions["baz"].load(); 38 | }); 39 | 40 | tearDown(() { 41 | if (client != null) client.close(); 42 | }); 43 | 44 | test("includes the script's metadata", () { 45 | expect(scriptRef.uri.scheme, equals("data")); 46 | expect(script.library.uri, equals(script.uri)); 47 | expect(script.source, contains("final foo = 1;")); 48 | expect(script.sourceFile.length, equals(script.source.length)); 49 | }); 50 | 51 | group("sourceLocation()", () { 52 | FileLocation fooLocation; 53 | FileLocation barLocation; 54 | FileLocation bazLocation; 55 | 56 | setUp(() { 57 | fooLocation = script.sourceLocation(foo.location.token); 58 | barLocation = script.sourceLocation(bar.location.token); 59 | bazLocation = script.sourceLocation(baz.location.token); 60 | }); 61 | 62 | test("looks up a source location", () { 63 | expect(fooLocation.file, same(script.sourceFile)); 64 | expect(fooLocation.sourceUrl, equals(script.uri)); 65 | expect(fooLocation.line, equals(2)); 66 | expect(fooLocation.column, equals(12)); 67 | expect(fooLocation.offset, equals(149)); 68 | }); 69 | 70 | group("compareTo()", () { 71 | test("is ordered by location", () { 72 | expect(fooLocation.compareTo(barLocation), lessThan(0)); 73 | expect(barLocation.compareTo(fooLocation), greaterThan(0)); 74 | expect(fooLocation.compareTo(fooLocation), equals(0)); 75 | }); 76 | 77 | test("works with foreign locations", () { 78 | var file = new SourceFile.fromString(script.source, url: script.uri); 79 | 80 | expect(barLocation.compareTo(file.location(fooLocation.offset)), 81 | greaterThan(0)); 82 | expect(barLocation.compareTo(file.location(barLocation.offset)), 83 | equals(0)); 84 | expect(barLocation.compareTo(file.location(bazLocation.offset)), 85 | lessThan(0)); 86 | }); 87 | 88 | test("throws for non-matching URLs", () async { 89 | var library = await isolate.libraries[Uri.parse("dart:core")].load(); 90 | var function = await library.functions["identical"].load(); 91 | var script = await function.location.script.load(); 92 | var span = script.sourceLocation(function.location.token); 93 | expect(() => fooLocation.compareTo(span), throwsArgumentError); 94 | }); 95 | 96 | test("throws for non-matching URLs on foreign locations", () { 97 | var file = new SourceFile.fromString(script.source, 98 | url: Uri.parse("other.dart")); 99 | expect(() => fooLocation.compareTo(file.location(fooLocation.offset)), 100 | throwsArgumentError); 101 | }); 102 | }); 103 | 104 | group("operator ==", () { 105 | test("returns true for matching locations", () { 106 | expect(fooLocation, equals(fooLocation)); 107 | }); 108 | 109 | test("returns true for matching foreign locations", () { 110 | var file = new SourceFile.fromString(script.source, url: script.uri); 111 | expect(fooLocation, equals(file.location(fooLocation.offset))); 112 | }); 113 | 114 | test("returns false for non-matching URLs on foreign locations", () { 115 | var file = new SourceFile.fromString(script.source, 116 | url: Uri.parse("other.dart")); 117 | expect(fooLocation, isNot(equals(file.location(fooLocation.offset)))); 118 | }); 119 | }); 120 | 121 | test("pointSpan() returns a point span at this location", () { 122 | var span = fooLocation.pointSpan(); 123 | expect(span.start, equals(fooLocation)); 124 | expect(span.end, equals(fooLocation)); 125 | }); 126 | }); 127 | 128 | group("sourceSpan()", () { 129 | FileSpan fooSpan; 130 | FileSpan barSpan; 131 | FileSpan bazSpan; 132 | 133 | setUp(() { 134 | fooSpan = script.sourceSpan(foo.location); 135 | barSpan = script.sourceSpan(bar.location); 136 | bazSpan = script.sourceSpan(baz.location); 137 | }); 138 | 139 | test("looks up a source span", () async { 140 | expect(barSpan.file, same(script.sourceFile)); 141 | expect(barSpan.sourceUrl, equals(script.uri)); 142 | expect(barSpan.length, equals(barSpan.text.length)); 143 | expect(barSpan.text, equals("int bar() => 1")); 144 | expect(barSpan.context, equals(" int bar() => 1; // line 4\n")); 145 | 146 | expect(barSpan.start.line, equals(4)); 147 | expect(barSpan.start.column, equals(6)); 148 | expect(barSpan.start.offset, equals(175)); 149 | 150 | expect(barSpan.end.line, equals(4)); 151 | expect(barSpan.end.column, equals(20)); 152 | expect(barSpan.end.offset, equals(189)); 153 | }); 154 | 155 | test("looks up a multiline source span", () async { 156 | expect(bazSpan.file, same(script.sourceFile)); 157 | expect(bazSpan.sourceUrl, equals(script.uri)); 158 | expect(bazSpan.length, equals(bazSpan.text.length)); 159 | 160 | // Bizarrely, the VM doesn't seem to include the final "}" in the source 161 | // location. 162 | expect( 163 | bazSpan.text, 164 | equals("int baz() { // line 6\n" 165 | " return 1;\n" 166 | " ")); 167 | expect( 168 | bazSpan.context, 169 | equals(" int baz() { // line 6\n" 170 | " return 1;\n" 171 | " }\n")); 172 | 173 | expect(bazSpan.start.line, equals(6)); 174 | expect(bazSpan.start.column, equals(6)); 175 | expect(bazSpan.start.offset, equals(208)); 176 | 177 | expect(bazSpan.end.line, equals(8)); 178 | expect(bazSpan.end.column, equals(6)); 179 | expect(bazSpan.end.offset, equals(254)); 180 | }); 181 | 182 | group("compareTo()", () { 183 | test("is ordered by start location", () { 184 | expect(fooSpan.compareTo(barSpan), lessThan(0)); 185 | expect(barSpan.compareTo(fooSpan), greaterThan(0)); 186 | expect(fooSpan.compareTo(fooSpan), equals(0)); 187 | }); 188 | 189 | test("works with foreign spans", () { 190 | var file = new SourceFile.fromString(script.source, url: script.uri); 191 | 192 | expect( 193 | barSpan 194 | .compareTo(file.span(fooSpan.start.offset, fooSpan.end.offset)), 195 | greaterThan(0)); 196 | expect( 197 | barSpan 198 | .compareTo(file.span(barSpan.start.offset, barSpan.end.offset)), 199 | equals(0)); 200 | expect( 201 | barSpan 202 | .compareTo(file.span(bazSpan.start.offset, bazSpan.end.offset)), 203 | lessThan(0)); 204 | }); 205 | 206 | test("throws for non-matching URLs", () async { 207 | var library = await isolate.libraries[Uri.parse("dart:core")].load(); 208 | var function = await library.functions["identical"].load(); 209 | var script = await function.location.script.load(); 210 | var span = script.sourceSpan(function.location); 211 | expect(() => fooSpan.compareTo(span), throwsArgumentError); 212 | }); 213 | 214 | test("throws for non-matching URLs on foreign spans", () { 215 | var file = new SourceFile.fromString(script.source, 216 | url: Uri.parse("other.dart")); 217 | var span = file.span(fooSpan.start.offset, fooSpan.end.offset); 218 | expect(() => fooSpan.compareTo(span), throwsArgumentError); 219 | }); 220 | }); 221 | 222 | group("operator ==", () { 223 | test("returns true for matching spans", () { 224 | expect(fooSpan, equals(fooSpan)); 225 | }); 226 | 227 | test("returns true for matching foreign spans", () { 228 | var file = new SourceFile.fromString(script.source, url: script.uri); 229 | expect(fooSpan, 230 | equals(file.span(fooSpan.start.offset, fooSpan.end.offset))); 231 | }); 232 | 233 | test("returns false for non-matching URLs on foreign spans", () { 234 | var file = new SourceFile.fromString(script.source, 235 | url: Uri.parse("other.dart")); 236 | var span = file.span(fooSpan.start.offset, fooSpan.end.offset); 237 | expect(fooSpan, isNot(equals(span))); 238 | }); 239 | }); 240 | 241 | group("union()", () { 242 | test("unions overlapping spans", () { 243 | var fooBar = fooSpan.expand(barSpan); 244 | var barBaz = barSpan.expand(bazSpan); 245 | 246 | var unioned = fooBar.union(barBaz); 247 | expect(unioned.start, equals(fooSpan.start)); 248 | expect(unioned.end, equals(bazSpan.end)); 249 | 250 | unioned = barBaz.union(fooBar); 251 | expect(unioned.start, equals(fooSpan.start)); 252 | expect(unioned.end, equals(bazSpan.end)); 253 | }); 254 | 255 | test("unions a script span with a foreign span", () { 256 | var file = new SourceFile.fromString(script.source, url: script.uri); 257 | var shiftedSpan = 258 | file.span(barSpan.start.offset + 1, barSpan.end.offset + 1); 259 | 260 | var unioned = barSpan.union(shiftedSpan); 261 | expect(unioned.start, equals(barSpan.start)); 262 | expect(unioned.end, equals(shiftedSpan.end)); 263 | }); 264 | 265 | test("throws for disjoint spans", () { 266 | expect(() => fooSpan.union(barSpan), throwsArgumentError); 267 | }); 268 | 269 | test("throws for non-matching URLs", () async { 270 | var library = await isolate.libraries[Uri.parse("dart:core")].load(); 271 | var function = await library.functions["identical"].load(); 272 | var script = await function.location.script.load(); 273 | var span = script.sourceSpan(function.location); 274 | expect(() => fooSpan.union(span), throwsArgumentError); 275 | }); 276 | 277 | test("throws for non-matching URLs on foreign spans", () { 278 | var file = new SourceFile.fromString(script.source, 279 | url: Uri.parse("other.dart")); 280 | var span = file.span(fooSpan.start.offset, fooSpan.end.offset); 281 | expect(() => fooSpan.union(span), throwsArgumentError); 282 | }); 283 | }); 284 | 285 | group("expand()", () { 286 | test("expands to cover another span", () { 287 | var expanded = fooSpan.expand(barSpan); 288 | expect(expanded.start, equals(fooSpan.start)); 289 | expect(expanded.end, equals(barSpan.end)); 290 | }); 291 | 292 | test("expands to cover a foreign span", () { 293 | var file = new SourceFile.fromString(script.source, url: script.uri); 294 | var span = file.span(barSpan.start.offset, barSpan.end.offset); 295 | 296 | var expanded = fooSpan.expand(span); 297 | expect(expanded.start, equals(fooSpan.start)); 298 | expect(expanded.end, equals(barSpan.end)); 299 | }); 300 | 301 | test("throws for non-matching URLs", () async { 302 | var library = await isolate.libraries[Uri.parse("dart:core")].load(); 303 | var function = await library.functions["identical"].load(); 304 | var script = await function.location.script.load(); 305 | var span = script.sourceSpan(function.location); 306 | expect(() => fooSpan.expand(span), throwsArgumentError); 307 | }); 308 | 309 | test("throws for non-matching URLs on foreign spans", () { 310 | var file = new SourceFile.fromString(script.source, 311 | url: Uri.parse("other.dart")); 312 | var span = file.span(barSpan.start.offset, barSpan.end.offset); 313 | expect(() => fooSpan.expand(span), throwsArgumentError); 314 | }); 315 | }); 316 | }); 317 | } 318 | -------------------------------------------------------------------------------- /test/source_report_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:source_span/source_span.dart'; 6 | import 'package:test/test.dart'; 7 | import 'package:vm_service_client/vm_service_client.dart'; 8 | 9 | import 'utils.dart'; 10 | 11 | const _mainContent = r""" 12 | print("one"); 13 | print("two"); 14 | 15 | if (false) { 16 | print("three"); 17 | print("four"); 18 | } 19 | 20 | Isolate.current.kill(); 21 | """; 22 | 23 | void main() { 24 | VMServiceClient client; 25 | VMIsolateRef isolate; 26 | 27 | tearDown(() { 28 | if (client != null) client.close(); 29 | }); 30 | 31 | group('getSourceReport for a script with one range', () { 32 | setUp(() async { 33 | client = await runAndConnect(main: _mainContent); 34 | 35 | isolate = (await client.getVM()).isolates.single; 36 | 37 | await isolate.waitUntilPaused(); 38 | }); 39 | 40 | test("returns a valid source report", () async { 41 | var report = await isolate.getSourceReport( 42 | includeCoverageReport: false, includePossibleBreakpoints: false); 43 | 44 | expect(report.ranges, hasLength(greaterThan(1))); 45 | 46 | var range = report.ranges.singleWhere((range) => 47 | range.script.uri.toString().startsWith('data:application/dart')); 48 | 49 | expect(range.compiled, isTrue); 50 | 51 | var script = await range.script.load(); 52 | 53 | var runnableIsolate = await isolate.loadRunnable(); 54 | 55 | var rootLib = await runnableIsolate.rootLibrary.load(); 56 | var mainFunction = await rootLib.functions['main'].load(); 57 | 58 | var mainLocation = script.sourceSpan(mainFunction.location); 59 | 60 | var startLocation = script.sourceLocation(range.location.token); 61 | expect(startLocation, mainLocation.start); 62 | 63 | var endLocation = script.sourceLocation(range.location.end); 64 | expect(endLocation, mainLocation.end); 65 | 66 | expect(range.hits, isNull); 67 | expect(range.misses, isNull); 68 | expect(range.possibleBreakpoints, isNull); 69 | }); 70 | 71 | test("reports accurate coverage information", () async { 72 | var report = 73 | await isolate.getSourceReport(includePossibleBreakpoints: false); 74 | 75 | var range = report.ranges.singleWhere((range) => 76 | range.script.uri.toString().startsWith('data:application/dart')); 77 | expect(range.possibleBreakpoints, isNull); 78 | 79 | var script = await range.script.load(); 80 | 81 | var hitLines = 82 | range.hits.map((token) => script.sourceLocation(token).line).toSet(); 83 | expect(hitLines, [ 84 | 4, 5, // preamble 85 | 7, // print("one"); 86 | 8, // print("two"); 87 | 15, // Isolate.current.kill(); 88 | ]); 89 | 90 | // The line that are not executed – two within the `if (false)` block 91 | var missedLines = 92 | range.misses.map((token) => script.sourceLocation(token).line); 93 | expect(missedLines, [11, 12]); 94 | }); 95 | 96 | test("reports accurate breakpoint information", () async { 97 | var report = await isolate.getSourceReport(includeCoverageReport: false); 98 | 99 | var range = report.ranges.singleWhere((range) => 100 | range.script.uri.toString().startsWith('data:application/dart')); 101 | 102 | expect(range.hits, isNull); 103 | expect(range.misses, isNull); 104 | 105 | var script = await range.script.load(); 106 | expect(range.possibleBreakpoints, isNotEmpty); 107 | 108 | // represents the unique set of lines that can have breakpoints 109 | var breakPointLines = range.possibleBreakpoints 110 | .map((token) => script.sourceLocation(token).line) 111 | .toSet(); 112 | expect(breakPointLines, [ 113 | 4, // main entry point 114 | 5, // preamble 115 | 7, // print("one"); 116 | 8, // print("two"); 117 | 11, // print("three"); 118 | 12, // print("four"); 119 | 15, // Isolate.current.kill(); 120 | 17 // VM considers the last line of an async function breakpoint-able 121 | ]); 122 | }); 123 | 124 | test("behaves correctly including coverage and breakpoints", () async { 125 | var report = await isolate.getSourceReport( 126 | includeCoverageReport: true, includePossibleBreakpoints: true); 127 | 128 | var range = report.ranges.singleWhere((range) => 129 | range.script.uri.toString().startsWith('data:application/dart')); 130 | 131 | expect(range.hits, isNotEmpty); 132 | expect(range.misses, isNotEmpty); 133 | expect(range.possibleBreakpoints, isNotEmpty); 134 | }); 135 | }); 136 | 137 | group('getSourceReport with a multi-range script', () { 138 | VMScript script; 139 | VMLibrary rootLib; 140 | VMSourceLocation mainLocation; 141 | FileSpan mainFunctionSpan; 142 | VMSourceLocation unusedFieldLocation; 143 | 144 | setUp(() async { 145 | client = await runAndConnect(topLevel: r'''final unusedField = 5; 146 | 147 | int unusedFunction(a, b) { 148 | return a + b; 149 | } 150 | 151 | void unusedFunction2(value) { 152 | print(value); 153 | }''', main: _mainContent); 154 | 155 | isolate = (await client.getVM()).isolates.single; 156 | 157 | await isolate.waitUntilPaused(); 158 | 159 | var runnableIsolate = await isolate.loadRunnable(); 160 | rootLib = await runnableIsolate.rootLibrary.load(); 161 | script = await rootLib.scripts.single.load(); 162 | 163 | var mainFunction = await rootLib.functions['main'].load(); 164 | mainLocation = mainFunction.location; 165 | mainFunctionSpan = script.sourceSpan(mainLocation); 166 | 167 | var unusedFieldRef = rootLib.fields['unusedField']; 168 | var unusedField = await unusedFieldRef.load(); 169 | unusedFieldLocation = unusedField.location; 170 | }); 171 | 172 | test("reports valid data with default arguments", () async { 173 | var report = await script.getSourceReport(); 174 | 175 | expect(report.ranges, hasLength(3)); 176 | 177 | var firstRange = report.ranges.first; 178 | expect(firstRange.compiled, isFalse); 179 | expect(firstRange.hits, isNull); 180 | expect(firstRange.misses, isNull); 181 | expect(firstRange.possibleBreakpoints, isNull); 182 | 183 | // TODO(kevmoo): it'd be nice if pkg/matcher had isBefore, isAfter 184 | // https://github.com/dart-lang/matcher/issues/34 185 | expect(script.sourceSpan(firstRange.location).compareTo(mainFunctionSpan), 186 | isNegative); 187 | 188 | var lastRange = report.ranges.last; 189 | expect(lastRange.compiled, isTrue); 190 | expect(script.sourceSpan(lastRange.location), equals(mainFunctionSpan)); 191 | }); 192 | 193 | test("reports all ranged compiled with forceCompile: true", () async { 194 | var report = await script.getSourceReport(forceCompile: true); 195 | 196 | expect(report.ranges, hasLength(3)); 197 | 198 | var firstRange = report.ranges.first; 199 | expect(firstRange.compiled, isTrue); 200 | 201 | var secondRange = report.ranges.last; 202 | expect(secondRange.compiled, isTrue); 203 | }); 204 | 205 | test("reports a valid subrange with the location argument", () async { 206 | var report = await script.getSourceReport(location: mainLocation); 207 | 208 | expect(script.sourceSpan(report.ranges.single.location), 209 | equals(mainFunctionSpan)); 210 | }); 211 | 212 | test("throws if a zero-length location is used", () async { 213 | expect(script.getSourceReport(location: unusedFieldLocation), 214 | throwsArgumentError); 215 | }); 216 | }); 217 | } 218 | -------------------------------------------------------------------------------- /test/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:convert'; 7 | import 'dart:io'; 8 | 9 | import 'package:async/async.dart'; 10 | import 'package:test/test.dart'; 11 | import 'package:vm_service_client/vm_service_client.dart'; 12 | 13 | final lines = new StreamTransformer( 14 | (Stream> stream, bool cancelOnError) => const LineSplitter() 15 | .bind(utf8.decoder.bind(stream)) 16 | .listen(null, cancelOnError: cancelOnError)); 17 | 18 | Future runAndConnect( 19 | {String topLevel, 20 | String main, 21 | List flags, 22 | bool sync: false}) async { 23 | if (topLevel == null) topLevel = ""; 24 | if (main == null) main = ""; 25 | if (flags == null) flags = []; 26 | 27 | // Put all imports on the first line so adding more doesn't break tests that 28 | // depend on specific line numbers. 29 | var imports = [ 30 | 'dart:async', 31 | 'dart:developer', 32 | 'dart:io', 33 | 'dart:isolate', 34 | 'dart:typed_data', 35 | 'dart:mirrors' 36 | ].map((uri) => 'import "$uri"').join('; '); 37 | 38 | // Similarly, put the preamble on one line so we can modify it without 39 | // breaking tests. 40 | var preamble = """ 41 | /* Wait for a line so the test doesn't print something that could be emitted 42 | * before "Observatory listening on". */ 43 | stdin.readLineSync(); 44 | 45 | /* Don't let the isolate close on its own. */ 46 | new ReceivePort(); 47 | """ 48 | .replaceAll("\n", " "); 49 | 50 | var library = """ 51 | $imports; 52 | 53 | $topLevel 54 | 55 | main() ${sync ? '' : 'async'} { 56 | $preamble 57 | 58 | $main 59 | } 60 | """; 61 | 62 | var uri = "data:application/dart;charset=utf-8,${Uri.encodeFull(library)}"; 63 | 64 | var args = flags.toList() 65 | ..addAll(['--pause-isolates-on-exit', '--enable-vm-service=0', uri]); 66 | var process = await Process.start(Platform.resolvedExecutable, args); 67 | 68 | var stdout = new StreamQueue(process.stdout.transform(lines)); 69 | var line = await stdout.next; 70 | 71 | // Start executing main(). 72 | process.stdin.writeln(); 73 | 74 | var match = new RegExp('Observatory listening on (.*)').firstMatch(line); 75 | var client = new VMServiceClient.connect(match[1]); 76 | client.done.then((_) => process.kill()); 77 | 78 | // Drain the rest of the stdout queue. Otherwise the stdout and stderr streams 79 | // won't work. 80 | stdout.rest.listen(null); 81 | 82 | return client; 83 | } 84 | 85 | Future sourceLine(VMBreakpointLocation location) async { 86 | var script = await location.script.load(); 87 | return script.sourceLocation(location.token).line; 88 | } 89 | 90 | /// Returns the first event on [stream] and asserts that it emits no more events 91 | /// until it closes. 92 | Future onlyEvent(Stream stream) { 93 | var completer = new Completer.sync(); 94 | stream.listen(expectAsync1(completer.complete, count: 1), 95 | onError: registerException, onDone: () { 96 | if (completer.isCompleted) return; 97 | throw "Expected an event."; 98 | }); 99 | 100 | // Wait a bit to see if any further events are emitted. 101 | expect(new Future.delayed(new Duration(milliseconds: 200)), completes); 102 | return completer.future; 103 | } 104 | -------------------------------------------------------------------------------- /test/vm_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:test/test.dart'; 6 | import 'package:vm_service_client/vm_service_client.dart'; 7 | 8 | import 'utils.dart'; 9 | 10 | VMServiceClient client; 11 | 12 | void main() { 13 | tearDown(() { 14 | if (client != null) client.close(); 15 | }); 16 | 17 | test("includes the VM's metadata", () async { 18 | var start = new DateTime.now(); 19 | client = await runAndConnect(); 20 | 21 | var vm = await client.getVM(); 22 | expect(vm.name, equals('vm')); 23 | expect(start.difference(vm.startTime).inMinutes, equals(0)); 24 | }); 25 | 26 | test("onUpdate fires when the VM's name changes", () async { 27 | client = await runAndConnect(); 28 | var vm = await client.getVM(); 29 | 30 | expect(vm.onUpdate.first.then((updated) => updated.name), 31 | completion(equals('fblthp'))); 32 | 33 | await vm.setName('fblthp'); 34 | }); 35 | 36 | test("setName() sets the VM's name", () async { 37 | client = await runAndConnect(); 38 | 39 | var vm = await client.getVM(); 40 | await vm.setName('fblthp'); 41 | expect((await vm.load()).name, equals('fblthp')); 42 | }); 43 | } 44 | --------------------------------------------------------------------------------