Skip to content

Commit 7a27735

Browse files
author
Anna Gringauze
authored
Remove evaluation code that was replaced by expression evaluator (#1673)
* Remove evaluation code that was replaced by expression evaluator * Update changelog * Addressed CR comments * Temporarily disable webdev evaluation tests
1 parent 6604468 commit 7a27735

File tree

9 files changed

+100
-132
lines changed

9 files changed

+100
-132
lines changed

dwds/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
## 16.0.0-dev
22
- Fix a hang and report errors on hot reload exceptions from the injected
33
client.
4+
- Remove `AppInspector.evaluate` code that has been replaced by expression
5+
evaluation using a compiler in all scenarios.
6+
- Fix a bug where evaluation would fail with more than one parameter in
7+
the scope.
8+
- Remove showing uncaptured values from the stack during evaluation.
49

510
**Breaking changes**
611
- Remove no longer used `ExpressionCompilerService.handler`.

dwds/lib/src/debugging/inspector.dart

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -251,22 +251,6 @@ class AppInspector extends Domain {
251251
return RemoteObject(result.result['result'] as Map<String, Object>);
252252
}
253253

254-
Future<RemoteObject> evaluate(
255-
String isolateId, String targetId, String expression,
256-
{Map<String, String> scope}) async {
257-
scope ??= {};
258-
final library = await getLibrary(isolateId, targetId);
259-
if (library == null) {
260-
throw UnsupportedError(
261-
'Evaluate is only supported when `targetId` is a library.');
262-
}
263-
if (scope.isNotEmpty) {
264-
return evaluateInLibrary(library, scope, expression);
265-
} else {
266-
return evaluateJsExpressionOnLibrary(expression, library.uri);
267-
}
268-
}
269-
270254
/// Invoke the function named [selector] on the object identified by
271255
/// [targetId].
272256
///
@@ -298,21 +282,6 @@ class AppInspector extends Domain {
298282
arguments);
299283
}
300284

301-
/// Evaluate [expression] as a member/message of the library identified by
302-
/// [libraryUri].
303-
///
304-
/// That is, we will just do 'library.$expression'
305-
Future<RemoteObject> evaluateJsExpressionOnLibrary(
306-
String expression, String libraryUri) {
307-
final evalExpression = '''
308-
(function() {
309-
${globalLoadStrategy.loadLibrarySnippet(libraryUri)};
310-
return library.$expression;
311-
})();
312-
''';
313-
return jsEvaluate(evalExpression);
314-
}
315-
316285
/// Evaluate [expression] by calling Chrome's Runtime.evaluate.
317286
Future<RemoteObject> jsEvaluate(String expression,
318287
{bool awaitPromise = false}) async {
@@ -343,21 +312,6 @@ class AppInspector extends Domain {
343312
return jsCallFunctionOn(remoteLibrary, jsFunction, arguments);
344313
}
345314

346-
/// Evaluate [expression] from [library] with [scope] as
347-
/// arguments.
348-
Future<RemoteObject> evaluateInLibrary(
349-
Library library, Map<String, String> scope, String expression) async {
350-
final argsString = scope.keys.join(', ');
351-
final arguments = scope.values.map(remoteObjectFor).toList();
352-
final evalExpression = '''
353-
function($argsString) {
354-
${globalLoadStrategy.loadLibrarySnippet(library.uri)};
355-
return library.$expression;
356-
}
357-
''';
358-
return _evaluateInLibrary(library, evalExpression, arguments);
359-
}
360-
361315
/// Call [function] with objects referred by [argumentIds] as arguments.
362316
Future<RemoteObject> callFunction(
363317
String function, Iterable<String> argumentIds) async {

dwds/lib/src/debugging/location.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,7 @@ class Locations {
166166
_entrypoint, Uri.parse(url).path);
167167
final cache = _moduleToLocations[module];
168168
if (cache != null) return cache;
169-
if (module == null) {
170-
_logger.warning('No module for $url');
171-
} else {
169+
if (module != null) {
172170
await _locationsForModule(module);
173171
}
174172
return _moduleToLocations[module] ?? {};

dwds/lib/src/services/chrome_proxy_service.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -462,10 +462,8 @@ ${globalLoadStrategy.loadModuleSnippet}("dart_sdk").developer.invokeExtension(
462462
isolateId, library.uri, expression, scope),
463463
expression);
464464
}
465-
// fall back to javascript evaluation
466-
final remote = await _inspector?.evaluate(isolateId, targetId, expression,
467-
scope: scope);
468-
return await _inspector?.instanceHelper?.instanceRefFor(remote);
465+
throw RPCError('evaluateInFrame', RPCError.kInvalidRequest,
466+
'Expression evaluation is not supported for this configuration.');
469467
}, (result) => DwdsEvent.evaluate(expression, result));
470468
}
471469

dwds/lib/src/services/expression_evaluator.dart

Lines changed: 79 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class ExpressionEvaluator {
7878
String expression,
7979
Map<String, String> scope,
8080
) async {
81+
scope ??= {};
8182
if (_compiler == null) {
8283
return _createError(ErrorKind.internal,
8384
'ExpressionEvaluator needs an ExpressionCompiler');
@@ -89,10 +90,8 @@ class ExpressionEvaluator {
8990

9091
final module = await _modules.moduleForlibrary(libraryUri);
9192

92-
if (scope != null && scope.isNotEmpty) {
93-
final params = scope.keys.join(', ');
94-
expression = '($params) => $expression';
95-
}
93+
// Wrap the expression in a lambda so we can call it as a function.
94+
expression = _createDartLambda(expression, scope.keys);
9695
_logger.finest('Evaluating "$expression" at $module');
9796

9897
// Compile expression using an expression compiler, such as
@@ -106,23 +105,13 @@ class ExpressionEvaluator {
106105
return _formatCompilationError(jsResult);
107106
}
108107

108+
// Strip try/catch incorrectly added by the expression compiler.
109+
var jsCode = _maybeStripTryCatch(jsResult);
110+
109111
// Send JS expression to chrome to evaluate.
110-
RemoteObject result;
111-
if (scope != null && scope.isNotEmpty) {
112-
// Strip try/catch.
113-
// TODO: remove adding try/catch block in expression compiler.
114-
// https://github.com/dart-lang/webdev/issues/1341
115-
final lines = jsResult.split('\n');
116-
final inner = lines.getRange(2, lines.length - 3).join('\n');
117-
final function = 'function(t) {'
118-
' return $inner(t);'
119-
'}';
120-
result = await _inspector.callFunction(function, scope.values);
121-
result = await _formatEvaluationError(result);
122-
} else {
123-
result = await _inspector.debugger.evaluate(jsResult);
124-
result = await _formatEvaluationError(result);
125-
}
112+
jsCode = _createJsLambdaWithTryCatch(jsCode, scope.keys);
113+
var result = await _inspector.callFunction(jsCode, scope.values);
114+
result = await _formatEvaluationError(result);
126115

127116
_logger.finest('Evaluated "$expression" to "$result"');
128117
return result;
@@ -207,30 +196,21 @@ class ExpressionEvaluator {
207196
return _formatCompilationError(jsResult);
208197
}
209198

199+
// Strip try/catch incorrectly added by the expression compiler.
200+
var jsCode = _maybeStripTryCatch(jsResult);
201+
202+
// Send JS expression to chrome to evaluate.
203+
jsCode = _createTryCatch(jsCode);
204+
210205
// Send JS expression to chrome to evaluate.
211206
var result = await _inspector.debugger
212-
.evaluateJsOnCallFrameIndex(frameIndex, jsResult);
207+
.evaluateJsOnCallFrameIndex(frameIndex, jsCode);
213208
result = await _formatEvaluationError(result);
214209

215210
_logger.finest('Evaluated "$expression" to "$result"');
216211
return result;
217212
}
218213

219-
String _valueToLiteral(RemoteObject value) {
220-
if (value.value == null) {
221-
return null;
222-
}
223-
224-
final ret = value.value.toString();
225-
if (value.type == 'string') {
226-
return '\'$ret\'';
227-
}
228-
229-
return ret;
230-
}
231-
232-
bool _isUndefined(RemoteObject value) => value.type == 'undefined';
233-
234214
RemoteObject _formatCompilationError(String error) {
235215
// Frontend currently gives a text message including library name
236216
// and function name on compilation error. Strip this information
@@ -286,28 +266,7 @@ class ExpressionEvaluator {
286266
for (var p in variables) {
287267
final name = p.name;
288268
final value = p.value;
289-
if (_isUndefined(value)) continue;
290-
291-
if (scopeType == 'closure') {
292-
// Substitute potentially unavailable captures with their values from
293-
// the stack.
294-
//
295-
// Note: this makes some uncaptured values available for evaluation,
296-
// which might not be formally correct but convenient, for evample:
297-
//
298-
// int x = 0;
299-
// var f = (int y) {
300-
// // 'x' is not captured so it not available at runtime but is
301-
// // captured on stack, so the code below will make it available
302-
// // for evaluation
303-
// print(y);
304-
// }
305-
// TODO(annagrin): decide if we would like not to support evaluation
306-
// of uncaptured variables
307-
308-
final capturedValue = _valueToLiteral(value);
309-
jsScope[name] = capturedValue ?? name;
310-
} else {
269+
if (!_isUndefined(value)) {
311270
jsScope[name] = name;
312271
}
313272
}
@@ -325,6 +284,68 @@ class ExpressionEvaluator {
325284
return jsScope;
326285
}
327286

287+
bool _isUndefined(RemoteObject value) =>
288+
value == null || value.type == 'undefined';
289+
290+
/// Strip try/catch incorrectly added by the expression compiler.
291+
/// TODO: remove adding try/catch block in expression compiler.
292+
/// https://github.com/dart-lang/webdev/issues/1341, then remove
293+
/// this stripping code.
294+
String _maybeStripTryCatch(String jsCode) {
295+
// Match the wrapping generated by the expression compiler exactly
296+
// so the maching does not succeed naturally after the wrapping is
297+
// removed:
298+
//
299+
// Expression compiler's wrapping:
300+
//
301+
// '\ntry {'
302+
// '\n ($jsExpression('
303+
// '\n $args'
304+
// '\n ))'
305+
// '\n} catch (error) {'
306+
// '\n error.name + ": " + error.message;'
307+
// '\n}';
308+
//
309+
final lines = jsCode.split('\n');
310+
if (lines.length > 5) {
311+
final tryLines = lines.getRange(0, 2).toList();
312+
final bodyLines = lines.getRange(2, lines.length - 3);
313+
final catchLines =
314+
lines.getRange(lines.length - 3, lines.length).toList();
315+
if (tryLines[0].isEmpty &&
316+
tryLines[1] == 'try {' &&
317+
catchLines[0] == '} catch (error) {' &&
318+
catchLines[1] == ' error.name + ": " + error.message;' &&
319+
catchLines[2] == '}') {
320+
return bodyLines.join('\n');
321+
}
322+
}
323+
return jsCode;
324+
}
325+
326+
String _createJsLambdaWithTryCatch(
327+
String expression, Iterable<String> params) {
328+
final args = params.join(', ');
329+
return ' '
330+
' function($args) {\n'
331+
' try {\n'
332+
' return $expression($args);\n'
333+
' } catch (error) {\n'
334+
' return error.name + ": " + error.message;\n'
335+
' }\n'
336+
'} ';
337+
}
338+
339+
String _createTryCatch(String expression) => ' '
340+
' try {\n'
341+
' $expression;\n'
342+
' } catch (error) {\n'
343+
' error.name + ": " + error.message;\n'
344+
' }\n';
345+
346+
String _createDartLambda(String expression, Iterable<String> params) =>
347+
'(${params.join(', ')}) => $expression';
348+
328349
/// Returns Chrome script uri for Chrome script ID.
329350
String _urlForScriptId(String scriptId) =>
330351
_inspector.remoteDebugger.scripts[scriptId]?.url;

dwds/test/chrome_proxy_service_test.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ void main() {
3636
group('shared context', () {
3737
setUpAll(() async {
3838
setCurrentLogWriter(debug: debug);
39-
await context.setUp(verboseCompiler: false);
39+
await context.setUp(
40+
enableExpressionEvaluation: true,
41+
verboseCompiler: false,
42+
);
4043
});
4144

4245
tearDownAll(() async {

dwds/test/fixtures/fakes.dart

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,6 @@ class FakeInspector extends Domain implements AppInspector {
5252
throw UnsupportedError('This is a fake');
5353
}
5454

55-
@override
56-
Future<RemoteObject> evaluate(
57-
String isolateId, String targetId, String expression,
58-
{Map<String, String> scope}) =>
59-
null;
60-
6155
@override
6256
Future<Obj> getObject(String isolateId, String objectId,
6357
{int offset, int count}) =>

dwds/test/inspector_test.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ void main() {
4545
Future<RemoteObject> libraryPublicFinal() =>
4646
inspector.jsEvaluate(libraryVariableExpression('libraryPublicFinal'));
4747

48+
Future<RemoteObject> libraryPrivate() =>
49+
inspector.jsEvaluate(libraryVariableExpression('_libraryPrivate'));
50+
4851
test('send toString', () async {
4952
final remoteObject = await libraryPublicFinal();
5053
final toString = await inspector.invokeMethod(remoteObject, 'toString', []);
@@ -107,8 +110,7 @@ void main() {
107110
setUp(() async {
108111
isolateId = inspector.isolate.id;
109112
bootstrapLibrary = inspector.isolate.rootLib;
110-
instance = await inspector.evaluate(
111-
isolateId, bootstrapLibrary.id, 'libraryPublicFinal');
113+
instance = await libraryPublicFinal();
112114
});
113115

114116
test('invoke top-level private', () async {
@@ -139,8 +141,7 @@ void main() {
139141
});
140142

141143
test('invoke instance method with object parameter 2', () async {
142-
final libraryPrivateList = await inspector.evaluate(
143-
isolateId, bootstrapLibrary.id, '_libraryPrivate');
144+
final libraryPrivateList = await libraryPrivate();
144145
final remote = await inspector.invoke(isolateId, instance.objectId,
145146
'equals', [libraryPrivateList.objectId]);
146147
expect(

webdev/test/e2e_test.dart

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -482,16 +482,10 @@ void main() {
482482

483483
await vmService.streamListen('Debug');
484484

485-
// Evaluate falls back to js evaluation
486-
var result = await vmService.evaluate(
487-
isolate.id, library.id, 'main.toString()');
488-
489485
expect(
490-
result,
491-
const TypeMatcher<InstanceRef>().having(
492-
(instance) => instance.valueAsString,
493-
'valueAsString',
494-
contains('Hello World!!')));
486+
() => vmService.evaluate(
487+
isolate.id, library.id, 'main.toString()'),
488+
throwsRPCError);
495489
} finally {
496490
await vmService?.dispose();
497491
await exitWebdev(process);
@@ -501,5 +495,5 @@ void main() {
501495
});
502496
});
503497
}
504-
});
498+
}, skip: 'dart-lang/sdk#49373');
505499
}

0 commit comments

Comments
 (0)