diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index 80ce3d895..b81c8d3db 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -12,7 +12,7 @@ restart. - Remove verbose printing on receiving DevTools events. - Update `vm_service` version to `^8.2.0`. - +- Update error message on expression evaluation using unloaded libraries. **Breaking changes:** - `Dwds.start` and `ExpressionCompilerService` now take diff --git a/dwds/debug_extension/pubspec.yaml b/dwds/debug_extension/pubspec.yaml index be7dd78db..c21957893 100644 --- a/dwds/debug_extension/pubspec.yaml +++ b/dwds/debug_extension/pubspec.yaml @@ -1,7 +1,6 @@ name: extension publish_to: none version: 1.27.0 -author: Dart Team homepage: https://github.com/dart-lang/webdev description: >- A chrome extension for Dart debugging. diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index b7502253a..eb337dd17 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -215,7 +215,8 @@ class ChromeProxyService implements VmServiceInterface { // the expression compiler service will fail to start. // Issue: https://github.com/dart-lang/webdev/issues/1282 var debugger = await _debugger; - await _initializeEntrypoint(appConnection.request.entrypointPath); + var entrypoint = appConnection.request.entrypointPath; + await _initializeEntrypoint(entrypoint); var sdkConfiguration = await _sdkConfigurationProvider.configuration; debugger.notifyPausedAtStart(); @@ -232,7 +233,13 @@ class ChromeProxyService implements VmServiceInterface { _expressionEvaluator = _compiler == null ? null - : ExpressionEvaluator(_inspector, _locations, _modules, _compiler); + : ExpressionEvaluator( + entrypoint, + _inspector, + _locations, + _modules, + _compiler, + ); await debugger.reestablishBreakpoints( _previousBreakpoints, _disabledBreakpoints); diff --git a/dwds/lib/src/services/expression_evaluator.dart b/dwds/lib/src/services/expression_evaluator.dart index 767283ee7..3ba1dfe06 100644 --- a/dwds/lib/src/services/expression_evaluator.dart +++ b/dwds/lib/src/services/expression_evaluator.dart @@ -11,6 +11,7 @@ import '../debugging/dart_scope.dart'; import '../debugging/inspector.dart'; import '../debugging/location.dart'; import '../debugging/modules.dart'; +import '../loaders/strategy.dart'; import '../utilities/objects.dart' as chrome; import 'expression_compiler.dart'; @@ -23,6 +24,7 @@ class ErrorKind { static const ErrorKind reference = ErrorKind._('ReferenceError'); static const ErrorKind internal = ErrorKind._('InternalError'); static const ErrorKind invalidInput = ErrorKind._('InvalidInputError'); + static const ErrorKind loadModule = ErrorKind._('LoadModuleError'); @override String toString() => _kind; @@ -33,17 +35,26 @@ class ErrorKind { /// collect context for evaluation (scope, types, modules), and using /// ExpressionCompilerInterface to compile dart expressions to JavaScript. class ExpressionEvaluator { + final String _entrypoint; final AppInspector _inspector; final Locations _locations; final Modules _modules; final ExpressionCompiler _compiler; final _logger = Logger('ExpressionEvaluator'); + /// Strip synthetic library name from compiler error messages. static final _syntheticNameFilterRegex = RegExp('org-dartlang-debug:synthetic_debug_expression:.*:.*Error: '); - ExpressionEvaluator( - this._inspector, this._locations, this._modules, this._compiler); + /// Find module path from the XHR call network error message received from chrome. + /// + /// Example: + /// NetworkError: Failed to load 'http://.com/path/to/module.js?' + static final _loadModuleErrorRegex = + RegExp(r".*Failed to load '.*\.com/(.*\.js).*"); + + ExpressionEvaluator(this._entrypoint, this._inspector, this._locations, + this._modules, this._compiler); RemoteObject _createError(ErrorKind severity, String message) { return RemoteObject( @@ -107,10 +118,10 @@ class ExpressionEvaluator { ' return $inner(t);' '}'; result = await _inspector.callFunction(function, scope.values); - result = _formatEvaluationError(result); + result = await _formatEvaluationError(result); } else { result = await _inspector.debugger.evaluate(jsResult); - result = _formatEvaluationError(result); + result = await _formatEvaluationError(result); } _logger.finest('Evaluated "$expression" to "$result"'); @@ -199,7 +210,7 @@ class ExpressionEvaluator { // Send JS expression to chrome to evaluate. var result = await _inspector.debugger .evaluateJsOnCallFrameIndex(frameIndex, jsResult); - result = _formatEvaluationError(result); + result = await _formatEvaluationError(result); _logger.finest('Evaluated "$expression" to "$result"'); return result; @@ -240,16 +251,26 @@ class ExpressionEvaluator { return _createError(ErrorKind.compilation, error); } - RemoteObject _formatEvaluationError(RemoteObject result) { + Future _formatEvaluationError(RemoteObject result) async { if (result.type == 'string') { var error = '${result.value}'; if (error.startsWith('ReferenceError: ')) { error = error.replaceFirst('ReferenceError: ', ''); return _createError(ErrorKind.reference, error); - } - if (error.startsWith('TypeError: ')) { + } else if (error.startsWith('TypeError: ')) { error = error.replaceFirst('TypeError: ', ''); return _createError(ErrorKind.type, error); + } else if (error.startsWith('NetworkError: ')) { + var modulePath = _loadModuleErrorRegex.firstMatch(error)?.group(1); + var module = modulePath != null + ? await globalLoadStrategy.moduleForServerPath( + _entrypoint, modulePath) + : 'unknown'; + modulePath ??= 'unknown'; + error = 'Module is not loaded : $module (path: $modulePath). ' + 'Accessing libraries that have not yet been used in the ' + 'application is not supported during expression evaluation.'; + return _createError(ErrorKind.loadModule, error); } } return result; diff --git a/dwds/test/build_daemon_evaluate_test.dart b/dwds/test/build_daemon_evaluate_test.dart index 7b6f98c10..4718ecebf 100644 --- a/dwds/test/build_daemon_evaluate_test.dart +++ b/dwds/test/build_daemon_evaluate_test.dart @@ -493,7 +493,7 @@ void main() async { }); }); - test('error', () async { + test('compilation error', () async { await onBreakPoint(isolate.id, mainScript, 'printLocal', () async { var event = await stream.firstWhere( (event) => event.kind == EventKind.kPauseBreakpoint); @@ -508,6 +508,21 @@ void main() async { }); }); + test('module load error', () async { + await onBreakPoint(isolate.id, mainScript, 'printLocal', () async { + var event = await stream.firstWhere( + (event) => event.kind == EventKind.kPauseBreakpoint); + + var error = await setup.service.evaluateInFrame( + isolate.id, event.topFrame.index, 'd.deferredPrintLocal()'); + + expect( + error, + isA().having((instance) => instance.message, + 'message', contains('LoadModuleError:'))); + }); + }, skip: 'https://github.com/dart-lang/sdk/issues/48587'); + test('cannot evaluate in unsupported isolate', () async { await onBreakPoint(isolate.id, mainScript, 'printLocal', () async { var event = await stream.firstWhere( diff --git a/dwds/test/frontend_server_evaluate_test.dart b/dwds/test/frontend_server_evaluate_test.dart index f5225c1f0..d9ad7febc 100644 --- a/dwds/test/frontend_server_evaluate_test.dart +++ b/dwds/test/frontend_server_evaluate_test.dart @@ -413,6 +413,21 @@ void main() async { contains('CompilationError:'))); }); }); + + test('module load error', () async { + await onBreakPoint(isolate.id, mainScript, 'printLocal', () async { + var event = await stream + .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint); + + var error = await setup.service.evaluateInFrame( + isolate.id, event.topFrame.index, 'd.deferredPrintLocal()'); + + expect( + error, + isA().having((instance) => instance.message, 'message', + contains('LoadModuleError:'))); + }); + }, skip: 'https://github.com/dart-lang/sdk/issues/48587'); }); group('evaluate', () { diff --git a/fixtures/_test/lib/deferred_library.dart b/fixtures/_test/lib/deferred_library.dart new file mode 100644 index 000000000..e550ee822 --- /dev/null +++ b/fixtures/_test/lib/deferred_library.dart @@ -0,0 +1,10 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// A library that we can import. +library test_deferred_library; + +void deferredPrintLocal() { + print('hello from deferred library'); // Breakpoint: DeferredPrintLocal +} diff --git a/fixtures/_testPackage/web/main.dart b/fixtures/_testPackage/web/main.dart index 65eefb3bd..d966da70d 100644 --- a/fixtures/_testPackage/web/main.dart +++ b/fixtures/_testPackage/web/main.dart @@ -8,6 +8,7 @@ import 'dart:async'; import 'dart:core'; import 'dart:html'; +import 'package:_test/deferred_library.dart' deferred as d; import 'package:_test/library.dart'; import 'package:_test_package/test_library.dart'; @@ -94,6 +95,10 @@ void printLoopVariable() { } } +Future printDeferred() async { + d.deferredPrintLocal(); +} + class MainClass { final int _field; MainClass(this._field); diff --git a/fixtures/_testPackageSound/web/main.dart b/fixtures/_testPackageSound/web/main.dart index c1e23472a..54aeaa1b2 100644 --- a/fixtures/_testPackageSound/web/main.dart +++ b/fixtures/_testPackageSound/web/main.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:core'; import 'dart:html'; +import 'package:_test/deferred_library.dart' deferred as d; import 'package:_test/library.dart'; import 'package:_test_package/test_library.dart'; @@ -103,6 +104,10 @@ void printLoopVariable() { } } +Future printDeferred() async { + d.deferredPrintLocal(); +} + class MainClass { final int _field; MainClass(this._field); diff --git a/fixtures/_testSound/lib/deferred_library.dart b/fixtures/_testSound/lib/deferred_library.dart new file mode 100644 index 000000000..e550ee822 --- /dev/null +++ b/fixtures/_testSound/lib/deferred_library.dart @@ -0,0 +1,10 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// A library that we can import. +library test_deferred_library; + +void deferredPrintLocal() { + print('hello from deferred library'); // Breakpoint: DeferredPrintLocal +} diff --git a/webdev/test/chrome_test.dart b/webdev/test/chrome_test.dart index f78018a8f..f21bee7d2 100644 --- a/webdev/test/chrome_test.dart +++ b/webdev/test/chrome_test.dart @@ -18,9 +18,11 @@ void main() { } tearDown(() async { - var tabs = await chrome.chromeConnection.getTabs(); - for (var tab in tabs) { - await chrome.chromeConnection.getUrl('/json/close/${tab.id}'); + var tabs = await chrome?.chromeConnection?.getTabs(); + if (tabs != null) { + for (var tab in tabs) { + await chrome.chromeConnection.getUrl('/json/close/${tab.id}'); + } } await chrome?.close(); chrome = null;