Skip to content

Commit 3f6928c

Browse files
derekxu16Commit Queue
authored andcommitted
[VM/Service] Correctly forward errors returned by external clients that handle 'compileExpression' requests
TEST=pkg/vm_service/test/forward_compile_expression_error_from_external_client_with_dds_test.dart and pkg/vm_service/test/forward_compile_expression_error_from_external_client_without_dds_test.dart CoreLibraryReviewExempt: This CL does not include any core library API changes, only VM Service implementation changes in sdk/lib/vmservice/running_isolates.dart. Fixes: #59603 Change-Id: I2b9edf69feb6149c80afe9fc753c73e069af0479 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/397580 Reviewed-by: Ben Konyi <[email protected]> Commit-Queue: Derek Xu <[email protected]>
1 parent e3ad3b5 commit 3f6928c

5 files changed

+126
-20
lines changed

pkg/pkg.status

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ vm_service/test/eval_*test: SkipByDesign # Debugger is disabled in AOT mode.
138138
vm_service/test/evaluate_*test: SkipByDesign # Debugger is disabled in AOT mode.
139139
vm_service/test/external_compilation_service_test: SkipByDesign # Spawns a secondary process.
140140
vm_service/test/field_script_test: SkipByDesign # Debugger is disabled in AOT mode.
141+
vm_service/test/forward_compile_expression_error_from_external_client_with_dds_test: SkipByDesign # Debugger is disabled in AOT mode.
142+
vm_service/test/forward_compile_expression_error_from_external_client_without_dds_test: SkipByDesign # Debugger is disabled in AOT mode.
141143
vm_service/test/get_allocation_traces_test: SkipByDesign # Debugger is disabled in AOT mode.
142144
vm_service/test/get_instances_as_*: SkipByDesign # Debugger is disabled in AOT mode
143145
vm_service/test/get_instances_rpc_test: SkipByDesign # Debugger is disabled in AOT mode.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) 2024, 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/vm_service.dart';
7+
import 'package:vm_service/vm_service_io.dart' show vmServiceConnectUri;
8+
9+
import 'common/service_test_common.dart';
10+
11+
final tests = <IsolateTest>[
12+
hasStoppedAtExit,
13+
(VmService primaryClient, IsolateRef isolateRef) async {
14+
const expressionCompilationFailedMessage = 'Expresion compilation failed';
15+
16+
final secondaryClient = await vmServiceConnectUri(primaryClient.wsUri!);
17+
secondaryClient.registerServiceCallback('compileExpression',
18+
(params) async {
19+
return {
20+
'jsonrpc': '2.0',
21+
'id': 0,
22+
'error': {
23+
'code': RPCErrorKind.kExpressionCompilationError.code,
24+
'message': expressionCompilationFailedMessage,
25+
'data': {'details': expressionCompilationFailedMessage},
26+
},
27+
};
28+
});
29+
await secondaryClient.registerService(
30+
'compileExpression',
31+
'Custom Expression Compilation',
32+
);
33+
34+
final isolateId = isolateRef.id!;
35+
try {
36+
final isolate = await primaryClient.getIsolate(isolateId);
37+
await primaryClient.evaluate(isolateId, isolate.rootLib!.id!, '123');
38+
fail('Expected to catch an RPCError');
39+
} on RPCError catch (e) {
40+
expect(e.code, RPCErrorKind.kExpressionCompilationError.code);
41+
// [e.details] used to be the string
42+
// "{code: 113, message: Expresion compilation failed, data: ...}", so we
43+
// want to avoid regressing to that behaviour.
44+
expect(e.details, expressionCompilationFailedMessage);
45+
}
46+
},
47+
];
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) 2024, 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+
// This is a regression test for https://dartbug.com/59603.
6+
7+
import 'common/test_helper.dart';
8+
import 'forward_compile_expression_error_from_external_client_test_common.dart';
9+
10+
void main([args = const <String>[]]) => runIsolateTests(
11+
args,
12+
tests,
13+
'forward_compile_expression_error_from_external_client_with_dds_test.dart',
14+
pauseOnExit: true,
15+
);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) 2024, 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+
// This is a regression test for https://dartbug.com/59603.
6+
7+
import 'common/test_helper.dart';
8+
import 'forward_compile_expression_error_from_external_client_test_common.dart';
9+
10+
void main([args = const <String>[]]) => runIsolateTests(
11+
args,
12+
tests,
13+
'forward_compile_expression_error_from_external_client_without_dds_test.dart',
14+
pauseOnExit: true,
15+
extraArgs: ['--no-dds'],
16+
);

sdk/lib/vmservice/running_isolates.dart

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
part of dart._vmservice;
66

7+
final class _CompileExpressionErrorDetails {
8+
final String details;
9+
10+
_CompileExpressionErrorDetails(this.details);
11+
}
12+
713
class RunningIsolates implements MessageRouter {
814
final isolates = <int, RunningIsolate>{};
915
int? _rootPortId;
@@ -96,8 +102,14 @@ class _Evaluator {
96102
kernelBase64 = await _compileExpression(
97103
responseJson['result'] as Map<String, dynamic>,
98104
);
99-
} catch (e) {
100-
return Response.from(encodeCompilationError(_message, e.toString()));
105+
} on _CompileExpressionErrorDetails catch (e) {
106+
return Response.from(
107+
encodeRpcError(
108+
_message,
109+
kExpressionCompilationError,
110+
details: e.details,
111+
),
112+
);
101113
}
102114
return await _evaluateCompiledExpression(kernelBase64);
103115
}
@@ -128,6 +140,26 @@ class _Evaluator {
128140
return _isolate.routeRequest(_service, buildScope);
129141
}
130142

143+
/// If [response] represents a valid JSON-RPC result, then this function
144+
/// returns the 'kernelBytes' property of that result. Otherwise, this
145+
/// function throws a [_CompileExpressionErrorDetails] object wrapping the
146+
/// 'details' property of the JSON-RPC error.
147+
static String _getKernelBytesOrThrowErrorDetails(
148+
Map<String, dynamic> response,
149+
) {
150+
if (response['result'] != null) {
151+
return (response['result'] as Map<String, dynamic>)['kernelBytes']
152+
as String;
153+
}
154+
final error = response['error'] as Map<String, dynamic>;
155+
final data = error['data'] as Map<String, dynamic>;
156+
throw _CompileExpressionErrorDetails(data['details']);
157+
}
158+
159+
/// If compilation fails, this method will throw a
160+
/// [_CompileExpressionErrorDetails] object that will be used to populate the
161+
/// 'details' field of the response to the evaluation RPC that requested this
162+
/// compilation to happen.
131163
Future<String> _compileExpression(
132164
Map<String, dynamic> buildScopeResponseResult,
133165
) {
@@ -182,14 +214,13 @@ class _Evaluator {
182214
}),
183215
),
184216
);
185-
return completer.future.then((s) => jsonDecode(s)).then((json) {
186-
final jsonMap = json as Map<String, dynamic>;
187-
if (jsonMap.containsKey('error')) {
188-
throw jsonMap['error'] as Object;
189-
}
190-
return (jsonMap['result'] as Map<String, dynamic>)['kernelBytes']
191-
as String;
192-
});
217+
return completer.future
218+
.then((s) => jsonDecode(s))
219+
.then(
220+
(json) => _getKernelBytesOrThrowErrorDetails(
221+
json as Map<String, dynamic>,
222+
),
223+
);
193224
} else {
194225
// fallback to compile using kernel service
195226
final compileExpressionParams = <String, dynamic>{
@@ -205,16 +236,11 @@ class _Evaluator {
205236
return _isolate
206237
.routeRequest(_service, compileExpression)
207238
.then((response) => response.decodeJson())
208-
.then((json) {
209-
final response = json as Map<String, dynamic>;
210-
if (response['result'] != null) {
211-
return (response['result'] as Map<String, dynamic>)['kernelBytes']
212-
as String;
213-
}
214-
final error = response['error'] as Map<String, dynamic>;
215-
final data = error['data'] as Map<String, dynamic>;
216-
throw data['details'] as Object;
217-
});
239+
.then(
240+
(json) => _getKernelBytesOrThrowErrorDetails(
241+
json as Map<String, dynamic>,
242+
),
243+
);
218244
}
219245
}
220246

0 commit comments

Comments
 (0)