Skip to content

Fix error message on expression evaluation for module load error #1542

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dwds/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion dwds/debug_extension/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: extension
publish_to: none
version: 1.27.0
author: Dart Team <[email protected]>
homepage: https://github.com/dart-lang/webdev
description: >-
A chrome extension for Dart debugging.
Expand Down
11 changes: 9 additions & 2 deletions dwds/lib/src/services/chrome_proxy_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
Expand Down
37 changes: 29 additions & 8 deletions dwds/lib/src/services/expression_evaluator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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;
Expand All @@ -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://<hostname>.com/path/to/module.js?<cache_busting_token>'
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(
Expand Down Expand Up @@ -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"');
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -240,16 +251,26 @@ class ExpressionEvaluator {
return _createError(ErrorKind.compilation, error);
}

RemoteObject _formatEvaluationError(RemoteObject result) {
Future<RemoteObject> _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;
Expand Down
17 changes: 16 additions & 1 deletion dwds/test/build_daemon_evaluate_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<ErrorRef>().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(
Expand Down
15 changes: 15 additions & 0 deletions dwds/test/frontend_server_evaluate_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<ErrorRef>().having((instance) => instance.message, 'message',
contains('LoadModuleError:')));
});
}, skip: 'https://github.com/dart-lang/sdk/issues/48587');
});

group('evaluate', () {
Expand Down
10 changes: 10 additions & 0 deletions fixtures/_test/lib/deferred_library.dart
Original file line number Diff line number Diff line change
@@ -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
}
5 changes: 5 additions & 0 deletions fixtures/_testPackage/web/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -94,6 +95,10 @@ void printLoopVariable() {
}
}

Future<void> printDeferred() async {
d.deferredPrintLocal();
}

class MainClass {
final int _field;
MainClass(this._field);
Expand Down
5 changes: 5 additions & 0 deletions fixtures/_testPackageSound/web/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -103,6 +104,10 @@ void printLoopVariable() {
}
}

Future<void> printDeferred() async {
d.deferredPrintLocal();
}

class MainClass {
final int _field;
MainClass(this._field);
Expand Down
10 changes: 10 additions & 0 deletions fixtures/_testSound/lib/deferred_library.dart
Original file line number Diff line number Diff line change
@@ -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
}
8 changes: 5 additions & 3 deletions webdev/test/chrome_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down