diff --git a/dwds/lib/src/debugging/dart_scope.dart b/dwds/lib/src/debugging/dart_scope.dart index b03106d4c..294bf041b 100644 --- a/dwds/lib/src/debugging/dart_scope.dart +++ b/dwds/lib/src/debugging/dart_scope.dart @@ -2,8 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -// @dart = 2.9 - import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; import '../utilities/objects.dart'; @@ -18,12 +16,12 @@ final ddcTemporaryVariableRegExp = RegExp(r'^(t[0-9]+\$?[0-9]*|__t[\$\w*]+)$'); /// /// See chromedevtools.github.io/devtools-protocol/tot/Debugger#type-CallFrame. Future> visibleProperties({ - Debugger debugger, - WipCallFrame frame, + required Debugger debugger, + required WipCallFrame frame, }) async { final allProperties = []; - if (frame.thisObject != null && frame.thisObject.type != 'undefined') { + if (frame.thisObject.type != 'undefined') { allProperties.add( Property({ 'name': 'this', @@ -39,11 +37,14 @@ Future> visibleProperties({ // Iterate to least specific scope last to help preserve order in the local // variables view when stepping. for (var scope in filterScopes(frame).reversed) { - final properties = await debugger.getProperties(scope.object.objectId); - allProperties.addAll(properties); + final objectId = scope.object.objectId; + if (objectId != null) { + final properties = await debugger.getProperties(objectId); + allProperties.addAll(properties); + } } - if (frame.returnValue != null && frame.returnValue.type != 'undefined') { + if (frame.returnValue != null && frame.returnValue!.type != 'undefined') { allProperties.add( Property({ 'name': 'return', @@ -54,15 +55,19 @@ Future> visibleProperties({ allProperties.removeWhere((property) { final value = property.value; + if (value == null) return true; + + final type = value.type; + final description = value.description ?? ''; + final name = property.name ?? ''; // TODO(#786) Handle these correctly rather than just suppressing them. // We should never see a raw JS class. The only case where this happens is a // Dart generic function, where the type arguments get passed in as // parameters. Hide those. - return (value.type == 'function' && - value.description.startsWith('class ')) || - (ddcTemporaryVariableRegExp.hasMatch(property.name)) || - (value.type == 'object' && value.description == 'dart.LegacyType.new'); + return (type == 'function' && description.startsWith('class ')) || + (ddcTemporaryVariableRegExp.hasMatch(name)) || + (type == 'object' && description == 'dart.LegacyType.new'); }); return allProperties; diff --git a/dwds/lib/src/debugging/debugger.dart b/dwds/lib/src/debugging/debugger.dart index 6b91e5e1c..a8f226e3f 100644 --- a/dwds/lib/src/debugging/debugger.dart +++ b/dwds/lib/src/debugging/debugger.dart @@ -2,13 +2,10 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -// @dart = 2.9 - import 'dart:async'; import 'dart:math' as math; import 'package:logging/logging.dart'; -import 'package:meta/meta.dart'; import 'package:pool/pool.dart'; import 'package:vm_service/vm_service.dart'; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart' @@ -82,7 +79,7 @@ class Debugger extends Domain { /// The most important thing here is that frames are identified by /// frameIndex in the Dart API, but by frame Id in Chrome, so we need /// to keep the JS frames and their Ids around. - FrameComputer stackComputer; + FrameComputer? stackComputer; bool _isStepping = false; @@ -99,11 +96,13 @@ class Debugger extends Domain { } Future setExceptionPauseMode(String mode) async { - mode = mode?.toLowerCase(); - if (!_pauseModePauseStates.containsKey(mode)) { + mode = mode.toLowerCase(); + final state = _pauseModePauseStates[mode]; + if (state == null) { throwInvalidParam('setExceptionPauseMode', 'Unsupported mode: $mode'); + } else { + _pauseState = state; } - _pauseState = _pauseModePauseStates[mode]; await _remoteDebugger.setPauseOnExceptions(_pauseState); return Success(); } @@ -121,11 +120,11 @@ class Debugger extends Domain { /// /// Note that stepping will automatically continue until Chrome is paused at /// a location for which we have source information. - Future resume({String step, int frameIndex}) async { + Future resume({String? step, int? frameIndex}) async { if (frameIndex != null) { throw ArgumentError('FrameIndex is currently unsupported.'); } - WipResponse result; + WipResponse? result; if (step != null) { _isStepping = true; switch (step) { @@ -154,13 +153,13 @@ class Debugger extends Domain { /// Returns null if the debugger is not paused. /// /// The returned stack will contain up to [limit] frames if provided. - Future getStack({int limit}) async { + Future getStack({int? limit}) async { if (stackComputer == null) { throw RPCError('getStack', RPCError.kInternalError, 'Cannot compute stack when application is not paused'); } - final frames = await stackComputer.calculateFrames(limit: limit); + final frames = await stackComputer!.calculateFrames(limit: limit); return Stack( frames: frames, messages: [], @@ -190,19 +189,19 @@ class Debugger extends Domain { // miss events. // Allow a null debugger/connection for unit tests. runZonedGuarded(() { - _remoteDebugger?.onPaused?.listen(_pauseHandler); - _remoteDebugger?.onResumed?.listen(_resumeHandler); - _remoteDebugger?.onTargetCrashed?.listen(_crashHandler); + _remoteDebugger.onPaused.listen(_pauseHandler); + _remoteDebugger.onResumed.listen(_resumeHandler); + _remoteDebugger.onTargetCrashed.listen(_crashHandler); }, (e, StackTrace s) { logger.warning('Error handling Chrome event', e, s); }); - handleErrorIfPresent(await _remoteDebugger?.enablePage()); - handleErrorIfPresent(await _remoteDebugger?.enable() as WipResponse); + handleErrorIfPresent(await _remoteDebugger.enablePage()); + handleErrorIfPresent(await _remoteDebugger.enable() as WipResponse); // Enable collecting information about async frames when paused. handleErrorIfPresent(await _remoteDebugger - ?.sendCommand('Debugger.setAsyncCallStackDepth', params: { + .sendCommand('Debugger.setAsyncCallStackDepth', params: { 'maxDepth': 128, })); } @@ -224,7 +223,7 @@ class Debugger extends Domain { Future addBreakpoint( String scriptId, int line, { - int column, + int? column, }) async { column ??= 0; final breakpoint = await _breakpoints.add(scriptId, line, column); @@ -232,9 +231,11 @@ class Debugger extends Domain { return breakpoint; } - Future _updatedScriptRefFor(Breakpoint breakpoint) async { + Future _updatedScriptRefFor(Breakpoint breakpoint) async { final oldRef = (breakpoint.location as SourceLocation).script; - final dartUri = DartUri(oldRef.uri, _root); + final uri = oldRef?.uri; + if (uri == null) return null; + final dartUri = DartUri(uri, _root); return await inspector.scriptRefFor(dartUri.serverPath); } @@ -245,24 +246,42 @@ class Debugger extends Domain { // Previous breakpoints were never removed from Chrome since we use // `setBreakpointByUrl`. We simply need to update the references. for (var breakpoint in previousBreakpoints) { + final dartBpId = breakpoint.id!; final scriptRef = await _updatedScriptRefFor(breakpoint); - final updatedLocation = await _locations.locationForDart( - DartUri(scriptRef.uri, _root), - _lineNumberFor(breakpoint), - _columnNumberFor(breakpoint)); - final updatedBreakpoint = _breakpoints._dartBreakpoint( - scriptRef, updatedLocation, breakpoint.id); - _breakpoints._note( - bp: updatedBreakpoint, - jsId: _breakpoints._jsIdByDartId[updatedBreakpoint.id]); - _notifyBreakpoint(updatedBreakpoint); + final scriptUri = scriptRef?.uri; + if (scriptRef != null && scriptUri != null) { + final jsBpId = _breakpoints.jsIdFor(dartBpId)!; + final updatedLocation = await _locations.locationForDart( + DartUri(scriptUri, _root), + _lineNumberFor(breakpoint), + _columnNumberFor(breakpoint)); + if (updatedLocation != null) { + final updatedBreakpoint = _breakpoints._dartBreakpoint( + scriptRef, updatedLocation, dartBpId); + _breakpoints._note(bp: updatedBreakpoint, jsId: jsBpId); + _notifyBreakpoint(updatedBreakpoint); + } else { + logger.warning('Cannot update breakpoint $dartBpId:' + ' cannot update location.'); + } + } else { + logger.warning('Cannot update breakpoint $dartBpId:' + ' cannot find script ref.'); + } } + // Disabled breakpoints were actually removed from Chrome so simply add // them back. for (var breakpoint in disabledBreakpoints) { - await addBreakpoint((await _updatedScriptRefFor(breakpoint)).id, - _lineNumberFor(breakpoint), - column: _columnNumberFor(breakpoint)); + final scriptRef = await _updatedScriptRefFor(breakpoint); + final scriptId = scriptRef?.id; + if (scriptId != null) { + await addBreakpoint(scriptId, _lineNumberFor(breakpoint), + column: _columnNumberFor(breakpoint)); + } else { + logger.warning('Cannot update disabled breakpoint ${breakpoint.id}:' + ' cannot find script ref.'); + } } } @@ -278,11 +297,15 @@ class Debugger extends Domain { /// Remove a Dart breakpoint. Future removeBreakpoint(String breakpointId) async { - if (!_breakpoints._bpByDartId.containsKey(breakpointId)) { + if (_breakpoints.breakpointFor(breakpointId) == null) { throwInvalidParam( 'removeBreakpoint', 'invalid breakpoint id $breakpointId'); } - final jsId = _breakpoints.jsId(breakpointId); + final jsId = _breakpoints.jsIdFor(breakpointId); + if (jsId == null) { + throw RPCError('removeBreakpoint', RPCError.kInternalError, + 'invalid JS breakpoint id $jsId'); + } await _removeBreakpoint(jsId); final bp = await _breakpoints.remove(jsId: jsId, dartId: breakpointId); @@ -310,29 +333,33 @@ class Debugger extends Domain { } /// Returns Chrome script uri for Chrome script ID. - String urlForScriptId(String scriptId) => + String? urlForScriptId(String scriptId) => _remoteDebugger.scripts[scriptId]?.url; /// Returns source [Location] for the paused event. /// /// If we do not have [Location] data for the embedded JS location, null is /// returned. - Future _sourceLocation(DebuggerPausedEvent e) { - final frame = e.params['callFrames'][0]; - final location = frame['location']; - final scriptId = location['scriptId'] as String; - final line = location['lineNumber'] as int; - final column = location['columnNumber'] as int; + Future _sourceLocation(DebuggerPausedEvent e) async { + final frame = e.params?['callFrames']?[0]; + final location = frame?['location']; + if (location == null) return null; + + final scriptId = location['scriptId'] as String?; + final line = location['lineNumber'] as int?; + if (scriptId == null || line == null) return null; + final column = location['columnNumber'] as int?; final url = urlForScriptId(scriptId); if (url == null) return null; + return _locations.locationForJs(url, line, column); } /// Returns script ID for the paused event. - String _frameScriptId(DebuggerPausedEvent e) { - final frame = e.params['callFrames'][0]; - return frame['location']['scriptId'] as String; + String? _frameScriptId(DebuggerPausedEvent e) { + final frame = e.params?['callFrames']?[0]; + return frame?['location']?['scriptId'] as String?; } /// The variables visible in a frame in Dart protocol [BoundVariable] form. @@ -347,26 +374,33 @@ class Debugger extends Domain { // JavaScript objects return boundVariables .where((bv) => bv != null && !isNativeJsObject(bv.value as InstanceRef)) - .toList(); - } - - Future _boundVariable(Property property) async { - // We return one level of properties from this object. Sub-properties are - // another round trip. - final instanceRef = await inspector.instanceRefFor(property.value); - // Skip null instance refs, which we get for weird objects, e.g. - // properties that are getter/setter pairs. - // TODO(alanknight): Handle these properly. - if (instanceRef == null) return null; - - return BoundVariable( - name: property.name, - value: instanceRef, - // TODO(grouma) - Provide actual token positions. - declarationTokenPos: -1, - scopeStartTokenPos: -1, - scopeEndTokenPos: -1, - ); + .toList() + .cast(); + } + + Future _boundVariable(Property property) async { + // TODO(annagrin): value might be null in the future for variables + // optimized by V8. Return appopriate sentinel values for them. + if (property.value != null) { + final value = property.value!; + // We return one level of properties from this object. Sub-properties are + // another round trip. + final instanceRef = await inspector.instanceRefFor(value); + // Skip null instance refs, which we get for weird objects, e.g. + // properties that are getter/setter pairs. + // TODO(alanknight): Handle these properly. + if (instanceRef == null) return null; + + return BoundVariable( + name: property.name, + value: instanceRef, + // TODO(grouma) - Provide actual token positions. + declarationTokenPos: -1, + scopeStartTokenPos: -1, + scopeEndTokenPos: -1, + ); + } + return null; } /// Find a sub-range of the entries for a Map/List when offset and/or count @@ -377,9 +411,7 @@ class Debugger extends Domain { /// [length]. If it is, then [length] should be the number of entries in the /// List/Map and [offset] and [count] should indicate the desired range. Future _subrange( - String id, int offset, int count, int length) async { - assert(offset != null); - assert(length != null); + String id, int offset, int? count, int length) async { // TODO(#809): Sometimes we already know the type of the object, and // we could take advantage of that to short-circuit. final receiver = remoteObjectFor(id); @@ -438,26 +470,28 @@ class Debugger extends Domain { /// List or Map, [offset] and/or [count] can be provided to indicate a desired /// range of entries. They will be ignored if there is no [length]. Future> getProperties(String objectId, - {int offset, int count, int length}) async { - var rangeId = objectId; + {int? offset, int? count, int? length}) async { + String rangeId = objectId; if (length != null && (offset != null || count != null)) { - final range = await _subrange(objectId, offset ?? 0, count, length); - rangeId = range.objectId; + final range = await _subrange(objectId, offset ?? 0, count ?? 0, length); + rangeId = range.objectId ?? rangeId; } - final response = - await _remoteDebugger.sendCommand('Runtime.getProperties', params: { - 'objectId': rangeId, - 'ownProperties': true, - }); - final jsProperties = response.result['result']; - final properties = (jsProperties as List) + final jsProperties = await sendCommandAndvalidateResult( + _remoteDebugger, + method: 'Runtime.getProperties', + resultField: 'result', + params: { + 'objectId': rangeId, + 'ownProperties': true, + }, + ); + return jsProperties .map((each) => Property(each as Map)) .toList(); - return properties; } /// Returns a Dart [Frame] for a JS [frame]. - Future calculateDartFrameFor( + Future calculateDartFrameFor( WipCallFrame frame, int frameIndex, { bool populateVariables = true, @@ -473,18 +507,17 @@ class Debugger extends Domain { return null; } - final bestLocation = await _locations.locationForJs(url, line, column); + final bestLocation = await _locations.locationForJs(url, line, column ?? 0); if (bestLocation == null) return null; final script = - await inspector?.scriptRefFor(bestLocation.dartLocation.uri.serverPath); + await inspector.scriptRefFor(bestLocation.dartLocation.uri.serverPath); // We think we found a location, but for some reason we can't find the // script. Just drop the frame. // TODO(#700): Understand when this can happen and have a better fix. if (script == null) return null; - final functionName = - _prettifyMember((frame.functionName ?? '').split('.').last); + final functionName = _prettifyMember((frame.functionName).split('.').last); final codeRefName = functionName.isEmpty ? '' : functionName; final dartFrame = Frame( @@ -512,10 +545,7 @@ class Debugger extends Domain { /// Handles pause events coming from the Chrome connection. Future _pauseHandler(DebuggerPausedEvent e) async { - if (inspector == null) return; - final isolate = inspector.isolate; - if (isolate == null) return; Event event; final timestamp = DateTime.now().millisecondsSinceEpoch; @@ -528,7 +558,7 @@ class Debugger extends Domain { .where((entry) => entry != null) .toSet(); final pauseBreakpoints = isolate.breakpoints - .where((bp) => breakpointIds.contains(bp.id)) + ?.where((bp) => breakpointIds.contains(bp.id)) .toList(); event = Event( kind: EventKind.kPauseBreakpoint, @@ -536,7 +566,7 @@ class Debugger extends Domain { isolate: inspector.isolateRef) ..pauseBreakpoints = pauseBreakpoints; } else if (e.reason == 'exception' || e.reason == 'assert') { - InstanceRef exception; + InstanceRef? exception; if (e.data is Map) { final map = e.data as Map; @@ -544,14 +574,11 @@ class Debugger extends Domain { // The className here is generally 'DartError'. final obj = RemoteObject(map); exception = await inspector.instanceRefFor(obj); - - // TODO: The exception object generally doesn't get converted to a - // Dart object (and instead has a classRef name of 'NativeJavaScriptObject'). - if (isNativeJsObject(exception)) { + if (exception != null && isNativeJsObject(exception)) { if (obj.description != null) { // Create a string exception object. final description = - await inspector.mapExceptionStackTrace(obj.description); + await inspector.mapExceptionStackTrace(obj.description!); exception = await inspector.instanceRefFor(description); } else { exception = null; @@ -571,8 +598,12 @@ class Debugger extends Domain { // avoiding stepping through library loading code. if (_isStepping) { final scriptId = _frameScriptId(e); + if (scriptId == null) { + logger.severe('Stepping failed: ' + 'cannot find script id for event $e'); + throw StateError('Stepping failed on event $e'); + } final url = urlForScriptId(scriptId); - if (url == null) { logger.severe('Stepping failed: ' 'cannot find url for script $scriptId'); @@ -608,7 +639,7 @@ class Debugger extends Domain { ); try { - final frames = await stackComputer.calculateFrames(limit: 1); + final frames = await stackComputer!.calculateFrames(limit: 1); event.topFrame = frames.isNotEmpty ? frames.first : null; } catch (e, s) { // TODO: Return information about the error to the user. @@ -624,11 +655,10 @@ class Debugger extends Domain { } /// Handles resume events coming from the Chrome connection. - Future _resumeHandler(DebuggerResumedEvent _) async { + Future _resumeHandler(DebuggerResumedEvent? _) async { // We can receive a resume event in the middle of a reload which will result // in a null isolate. - final isolate = inspector?.isolate; - if (isolate == null) return; + final isolate = inspector.isolate; stackComputer = null; final event = Event( @@ -648,8 +678,7 @@ class Debugger extends Domain { Future _crashHandler(TargetCrashedEvent _) async { // We can receive a resume event in the middle of a reload which will result // in a null isolate. - final isolate = inspector?.isolate; - if (isolate == null) return; + final isolate = inspector.isolate; stackComputer = null; final event = Event( @@ -661,12 +690,13 @@ class Debugger extends Domain { logger.severe('Target crashed!'); } - WipCallFrame jsFrameForIndex(int frameIndex) { - if (stackComputer == null) { + WipCallFrame? jsFrameForIndex(int frameIndex) { + final computer = stackComputer; + if (computer == null) { throw RPCError('evaluateInFrame', 106, 'Cannot evaluate on a call frame when the program is not paused'); } - return stackComputer.jsFrameForIndex(frameIndex); + return computer.jsFrameForIndex(frameIndex); } /// Evaluate [expression] by calling Chrome's Runtime.evaluateOnCallFrame on @@ -676,8 +706,12 @@ class Debugger extends Domain { /// [StateError]. Future evaluateJsOnCallFrameIndex( int frameIndex, String expression) { - return evaluateJsOnCallFrame( - jsFrameForIndex(frameIndex).callFrameId, expression); + final index = jsFrameForIndex(frameIndex)?.callFrameId; + if (index == null) { + // This might happen on async frames. + throw StateError('No frame for frame index: $index'); + } + return evaluateJsOnCallFrame(index, expression); } /// Evaluate [expression] by calling Chrome's Runtime.evaluateOnCallFrame on @@ -699,24 +733,39 @@ class Debugger extends Domain { } } +Future sendCommandAndvalidateResult( + RemoteDebugger remoteDebugger, { + required String method, + required String resultField, + Map? params, +}) async { + final response = await remoteDebugger.sendCommand(method, params: params); + final result = response.result?[resultField]; + if (result == null) { + throw RPCError(method, RPCError.kInternalError, + '$resultField not found in result from sendCommand', params); + } + return result; +} + bool isNativeJsObject(InstanceRef instanceRef) { // New type representation of JS objects reifies them to a type suffixed with // JavaScriptObject. - final className = instanceRef?.classRef?.name; + final className = instanceRef.classRef?.name; return (className != null && className.endsWith('JavaScriptObject') && - instanceRef?.classRef?.library?.uri == 'dart:_interceptors') || + instanceRef.classRef?.library?.uri == 'dart:_interceptors') || // Old type representation still needed to support older SDK versions. className == 'NativeJavaScriptObject'; } /// Returns the Dart line number for the provided breakpoint. int _lineNumberFor(Breakpoint breakpoint) => - int.parse(breakpoint.id.split('#').last.split(':').first); + int.parse(breakpoint.id!.split('#').last.split(':').first); /// Returns the Dart column number for the provided breakpoint. int _columnNumberFor(Breakpoint breakpoint) => - int.parse(breakpoint.id.split('#').last.split(':').last); + int.parse(breakpoint.id!.split('#').last.split(':').last); /// Returns the breakpoint ID for the provided Dart script ID and Dart line /// number. @@ -740,20 +789,24 @@ class _Breakpoints extends Domain { final String root; _Breakpoints({ - @required this.locations, - @required this.remoteDebugger, - @required this.root, + required this.locations, + required this.remoteDebugger, + required this.root, }); Future _createBreakpoint( String id, String scriptId, int line, int column) async { final dartScript = inspector.scriptWithId(scriptId); - final dartUri = DartUri(dartScript.uri, root); - final location = await locations.locationForDart(dartUri, line, column); + final dartScriptUri = dartScript?.uri; + Location? location; + if (dartScriptUri != null) { + final dartUri = DartUri(dartScriptUri, root); + location = await locations.locationForDart(dartUri, line, column); + } // TODO: Handle cases where a breakpoint can't be set exactly at that line. if (location == null) { _logger.fine('Failed to set breakpoint $id ' - '(${dartUri.serverPath}:$line:$column): ' + '($scriptId:$line:$column): ' 'cannot find Dart location.'); throw RPCError( 'addBreakpoint', @@ -763,9 +816,18 @@ class _Breakpoints extends Domain { } try { - final dartBreakpoint = _dartBreakpoint(dartScript, location, id); + final dartBreakpoint = _dartBreakpoint(dartScript!, location, id); final jsBreakpointId = await _setJsBreakpoint(location); - + if (jsBreakpointId == null) { + _logger.fine('Failed to set breakpoint $id ' + '($scriptId:$line:$column): ' + 'cannot set JS breakpoint.'); + throw RPCError( + 'addBreakpoint', + 102, + 'The VM is unable to add a breakpoint ' + 'at the specified line or function'); + } _note(jsId: jsBreakpointId, bp: dartBreakpoint); return dartBreakpoint; } on WipError catch (wipError) { @@ -800,43 +862,50 @@ class _Breakpoints extends Domain { } /// Calls the Chrome protocol setBreakpoint and returns the remote ID. - Future _setJsBreakpoint(Location location) async { + Future _setJsBreakpoint(Location location) async { // The module can be loaded from a nested path and contain an ETAG suffix. final urlRegex = '.*${location.jsLocation.module}.*'; // Prevent `Aww, snap!` errors when setting multiple breakpoints // simultaneously by serializing the requests. return _pool.withResource(() async { - final response = await remoteDebugger - .sendCommand('Debugger.setBreakpointByUrl', params: { - 'urlRegex': urlRegex, - 'lineNumber': location.jsLocation.line, - 'columnNumber': location.jsLocation.column, - }); - return response.result['breakpointId'] as String; + final breakPointId = await sendCommandAndvalidateResult( + remoteDebugger, + method: 'Debugger.setBreakpointByUrl', + resultField: 'breakpointId', + params: { + 'urlRegex': urlRegex, + 'lineNumber': location.jsLocation.line, + 'columnNumber': location.jsLocation.column, + }); + return breakPointId; }); } /// Records the internal Dart <=> JS breakpoint id mapping and adds the /// breakpoint to the current isolates list of breakpoints. - void _note({@required Breakpoint bp, @required String jsId}) { - _dartIdByJsId[jsId] = bp.id; - _jsIdByDartId[bp.id] = jsId; - final isolate = inspector.isolate; - isolate?.breakpoints?.add(bp); + void _note({required Breakpoint bp, required String jsId}) { + final bpId = bp.id; + if (bpId != null) { + _dartIdByJsId[jsId] = bpId; + _jsIdByDartId[bpId] = jsId; + final isolate = inspector.isolate; + isolate.breakpoints?.add(bp); + } } - Future remove({ - @required String jsId, - @required String dartId, + Future remove({ + required String jsId, + required String dartId, }) async { final isolate = inspector.isolate; _dartIdByJsId.remove(jsId); _jsIdByDartId.remove(dartId); - isolate?.breakpoints?.removeWhere((b) => b.id == dartId); + isolate.breakpoints?.removeWhere((b) => b.id == dartId); return await _bpByDartId.remove(dartId); } - String jsId(String dartId) => _jsIdByDartId[dartId]; + Future? breakpointFor(String dartId) => _bpByDartId[dartId]; + String? jsIdFor(String dartId) => _jsIdByDartId[dartId]; } final escapedPipe = '\$124'; diff --git a/dwds/lib/src/debugging/frame_computer.dart b/dwds/lib/src/debugging/frame_computer.dart index a3b582c3f..22573a979 100644 --- a/dwds/lib/src/debugging/frame_computer.dart +++ b/dwds/lib/src/debugging/frame_computer.dart @@ -2,9 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -// @dart = 2.9 - -import 'package:logging/logging.dart'; import 'package:pool/pool.dart'; import 'package:vm_service/vm_service.dart'; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; @@ -14,8 +11,6 @@ import 'debugger.dart'; class FrameComputer { final Debugger debugger; - final _logger = Logger('FrameComputer'); - // To ensure that the frames are computed only once, we use a pool to guard // the work. Frames are computed sequentially. final _pool = Pool(1); @@ -25,14 +20,14 @@ class FrameComputer { var _frameIndex = 0; - StackTrace _asyncStackTrace; - List _asyncFramesToProcess; + StackTrace? _asyncStackTrace; + List? _asyncFramesToProcess; - FrameComputer(this.debugger, this._callFrames, {StackTrace asyncStackTrace}) + FrameComputer(this.debugger, this._callFrames, {StackTrace? asyncStackTrace}) : _asyncStackTrace = asyncStackTrace; /// Given a frame index, return the corresponding JS frame. - WipCallFrame jsFrameForIndex(int frameIndex) { + WipCallFrame? jsFrameForIndex(int frameIndex) { // Clients can send us indices greater than the number of JS frames as async // frames don't have corresponding WipCallFrames. return frameIndex < _callFrames.length ? _callFrames[frameIndex] : null; @@ -40,7 +35,7 @@ class FrameComputer { /// Translates Chrome callFrames contained in [DebuggerPausedEvent] into Dart /// [Frame]s. - Future> calculateFrames({int limit}) async { + Future> calculateFrames({int? limit}) async { return _pool.withResource(() async { if (limit != null && _computedFrames.length >= limit) { return _computedFrames.take(limit).toList(); @@ -61,7 +56,7 @@ class FrameComputer { }); } - Future _collectSyncFrames({int limit}) async { + Future _collectSyncFrames({int? limit}) async { while (_frameIndex < _callFrames.length) { if (limit != null && _computedFrames.length == limit) return; @@ -74,10 +69,11 @@ class FrameComputer { } } - Future _collectAsyncFrames({int limit}) async { + Future _collectAsyncFrames({int? limit}) async { if (_asyncStackTrace == null) return; while (_asyncStackTrace != null) { + final asyncStackTrace = _asyncStackTrace!; if (limit != null && _computedFrames.length == limit) { return; } @@ -89,23 +85,16 @@ class FrameComputer { _computedFrames.add(Frame( index: _frameIndex++, kind: FrameKind.kAsyncSuspensionMarker)); } - _asyncFramesToProcess = _asyncStackTrace.callFrames; + _asyncFramesToProcess = asyncStackTrace.callFrames; } else { + final asyncFramesToProcess = _asyncFramesToProcess!; // Process a single async frame. - if (_asyncFramesToProcess.isNotEmpty) { - final callFrame = _asyncFramesToProcess.removeAt(0); + if (asyncFramesToProcess.isNotEmpty) { + final callFrame = asyncFramesToProcess.removeAt(0); final location = WipLocation.fromValues( callFrame.scriptId, callFrame.lineNumber, columnNumber: callFrame.columnNumber); - final url = - callFrame.url ?? debugger.urlForScriptId(location.scriptId); - if (url == null) { - _logger.severe( - 'Failed to create dart frame for ${callFrame.functionName}: ' - 'cannot find location for script ${callFrame.scriptId}'); - } - final tempWipFrame = WipCallFrame({ 'url': callFrame.url, 'functionName': callFrame.functionName, @@ -127,8 +116,8 @@ class FrameComputer { // Async frames are no longer on the stack - we don't have local variable // information for them. - if (_asyncFramesToProcess.isEmpty) { - _asyncStackTrace = _asyncStackTrace.parent; + if (_asyncFramesToProcess!.isEmpty) { + _asyncStackTrace = asyncStackTrace.parent; _asyncFramesToProcess = null; } } diff --git a/dwds/lib/src/debugging/location.dart b/dwds/lib/src/debugging/location.dart index 52408df50..5bf69980c 100644 --- a/dwds/lib/src/debugging/location.dart +++ b/dwds/lib/src/debugging/location.dart @@ -183,7 +183,7 @@ class Locations { /// Find the [Location] for the given JS source position. /// /// The [line] number is 0-based. - Future locationForJs(String url, int line, int column) async { + Future locationForJs(String url, int line, int? column) async { final locations = await locationsForUrl(url); return _bestJsLocation(locations, line, column); } @@ -218,7 +218,8 @@ class Locations { /// /// https://github.com/microsoft/vscode-js-debug/blob/536f96bae61a3d87546b61bc7916097904c81429/src/common/sourceUtils.ts#L286 Location? _bestJsLocation( - Iterable locations, int line, int column) { + Iterable locations, int line, int? column) { + column ??= 0; Location? bestLocation; for (var location in locations) { if (location.jsLocation.compareToLine(line, column) <= 0) { diff --git a/dwds/lib/src/debugging/skip_list.dart b/dwds/lib/src/debugging/skip_list.dart index b4a57fc2a..ae64e5ea7 100644 --- a/dwds/lib/src/debugging/skip_list.dart +++ b/dwds/lib/src/debugging/skip_list.dart @@ -2,8 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -// @dart = 2.9 - import 'location.dart'; const maxValue = 2147483647; @@ -24,7 +22,7 @@ class SkipLists { String scriptId, Set locations, ) async { - if (_idToList.containsKey(scriptId)) return _idToList[scriptId]; + if (_idToList.containsKey(scriptId)) return _idToList[scriptId]!; final sortedLocations = locations.toList() ..sort((a, b) => a.jsLocation.compareTo(b.jsLocation)); diff --git a/dwds/lib/src/utilities/domain.dart b/dwds/lib/src/utilities/domain.dart index b4df704ce..72edc3928 100644 --- a/dwds/lib/src/utilities/domain.dart +++ b/dwds/lib/src/utilities/domain.dart @@ -2,8 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file.import 'dart:async'; -// @dart = 2.9 - import 'package:vm_service/vm_service.dart'; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; @@ -33,16 +31,16 @@ abstract class AppInspectorInterface { {bool returnByValue = false}); /// Returns the [ScriptRef] for the provided Dart server path [uri]. - Future scriptRefFor(String uri); + Future scriptRefFor(String uri); /// Look up the script by id in an isolate. - ScriptRef scriptWithId(String scriptId); + ScriptRef? scriptWithId(String scriptId); /// Returns the [LibraryRef] for the provided Dart [objectId]. - Future libraryRefFor(String objectId); + Future libraryRefFor(String objectId); /// Returns the [Library] for the provided Dart [objectId]. - Future getLibrary(String objectId); + Future getLibrary(String objectId); /// Returns the [ScriptRef]s in the library with [libraryId]. Future> scriptRefsForLibrary(String libraryId); @@ -50,10 +48,10 @@ abstract class AppInspectorInterface { /// Create an InstanceRef for an object, which may be a RemoteObject, or may /// be something returned by value from Chrome, e.g. number, boolean, or /// String. - Future instanceRefFor(Object value); + Future instanceRefFor(Object value); /// Get the value of the field named [fieldName] from [receiver]. - Future loadField(RemoteObject receiver, String fieldName); + Future loadField(RemoteObject receiver, String fieldName); /// Convert a JS exception description into a description containing /// a Dart stack trace. @@ -106,7 +104,7 @@ abstract class AppInspectorInterface { abstract class Domain { Domain(); - AppInspectorInterface inspector; + late AppInspectorInterface inspector; } void throwInvalidParam(String method, String message) { diff --git a/dwds/test/chrome_proxy_service_test.dart b/dwds/test/chrome_proxy_service_test.dart index f410e2819..e60c10cdb 100644 --- a/dwds/test/chrome_proxy_service_test.dart +++ b/dwds/test/chrome_proxy_service_test.dart @@ -1160,7 +1160,7 @@ void main() { await service.resume(isolateId); }); - test('returns non-empty stack when paused', () async { + test('returns non-null stack when paused', () async { await service.pause(isolateId); // Wait for pausing to actually propagate. await stream diff --git a/fixtures/_testPackage/web/main.dart b/fixtures/_testPackage/web/main.dart index 2ac7b802f..f9e5eb6dd 100644 --- a/fixtures/_testPackage/web/main.dart +++ b/fixtures/_testPackage/web/main.dart @@ -109,6 +109,7 @@ void printNestedObjectsMultiLine() { } void printObjectMultiLine() { + // Note: formatting the line below breaks callstack tests. print( // Breakpoint: printMultiLine createObject() // Breakpoint: printObjectMultiLine ..initialize(), diff --git a/fixtures/_testPackageSound/web/main.dart b/fixtures/_testPackageSound/web/main.dart index 1552cb314..221b6d1e0 100644 --- a/fixtures/_testPackageSound/web/main.dart +++ b/fixtures/_testPackageSound/web/main.dart @@ -118,6 +118,7 @@ void printNestedObjectsMultiLine() { } void printObjectMultiLine() { + // Note: formatting the line below breaks callstack tests. print( // Breakpoint: printMultiLine createObject() // Breakpoint: printObjectMultiLine ..initialize(),