[ package:dds ] Handle expression evaluation invocations in DDS and forward compileExpression calls to clients which provide their own compilation service.

Change-Id: I2daec26929ad4f530d28d0073a0b2758850bec0a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/150694
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
Ben Konyi 2020-06-11 22:43:39 +00:00 committed by commit-bot@chromium.org
parent f0ea02bc51
commit c54ec8d5f3
14 changed files with 179 additions and 9 deletions

View file

@ -1,6 +1,11 @@
# 1.2.1
- Fixed issue where `evaluate` and `evaluateInFrame` were not invoking client
provided implementations of `compileExpression`.
# 1.2.0
- Fix issue where forwarding requests with no RPC parameters would return an
- Fixed issue where forwarding requests with no RPC parameters would return an
RPC error.
# 1.1.0

View file

@ -29,6 +29,7 @@ part 'src/client.dart';
part 'src/client_manager.dart';
part 'src/constants.dart';
part 'src/dds_impl.dart';
part 'src/expression_evaluator.dart';
part 'src/logging_repository.dart';
part 'src/isolate_manager.dart';
part 'src/named_lookup.dart';

View file

@ -157,6 +157,19 @@ class _DartDevelopmentServiceClient {
return supportedProtocols;
});
// `evaluate` and `evaluateInFrame` actually consist of multiple RPC
// invocations, including a call to `compileExpression` which can be
// overridden by clients which provide their own implementation (e.g.,
// Flutter Tools). We handle all of this in [_ExpressionEvaluator].
_clientPeer.registerMethod(
'evaluate',
dds.expressionEvaluator.execute,
);
_clientPeer.registerMethod(
'evaluateInFrame',
dds.expressionEvaluator.execute,
);
// When invoked within a fallback, the next fallback will start executing.
// The final fallback forwards the request to the VM service directly.
@alwaysThrows

View file

@ -133,6 +133,16 @@ class _ClientManager {
}
}
_DartDevelopmentServiceClient findFirstClientThatHandlesService(
String service) {
for (final client in clients) {
if (client.services.containsKey(service)) {
return client;
}
}
return null;
}
// Handles namespace generation for service extensions.
static const _kServicePrologue = 's';
final NamedLookup<_DartDevelopmentServiceClient> clients = NamedLookup(

View file

@ -11,6 +11,7 @@ class _DartDevelopmentService implements DartDevelopmentService {
this._authCodesEnabled,
) {
_clientManager = _ClientManager(this);
_expressionEvaluator = _ExpressionEvaluator(this);
_isolateManager = _IsolateManager(this);
_loggingRepository = _LoggingRepository();
_streamManager = _StreamManager(this);
@ -197,6 +198,9 @@ class _DartDevelopmentService implements DartDevelopmentService {
_ClientManager get clientManager => _clientManager;
_ClientManager _clientManager;
_ExpressionEvaluator get expressionEvaluator => _expressionEvaluator;
_ExpressionEvaluator _expressionEvaluator;
_IsolateManager get isolateManager => _isolateManager;
_IsolateManager _isolateManager;

View file

@ -0,0 +1,128 @@
// Copyright (c) 2020, 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.
part of dds;
/// A helper class which handles `evaluate` and `evaluateInFrame` calls by
/// potentially forwarding compilation requests to an external compilation
/// service like Flutter Tools.
class _ExpressionEvaluator {
_ExpressionEvaluator(this.dds);
Future<Map<String, dynamic>> execute(json_rpc.Parameters parameters) async {
final isolateId = parameters['isolateId'].asString;
final expression = parameters['expression'].asString;
Map<String, dynamic> buildScopeResponse;
try {
buildScopeResponse = await _buildScope(parameters);
} on json_rpc.RpcException catch (e) {
throw _RpcErrorCodes.buildRpcException(
_RpcErrorCodes.kExpressionCompilationError,
data: e.data,
);
}
String kernelBase64;
try {
kernelBase64 =
await _compileExpression(isolateId, expression, buildScopeResponse);
} on json_rpc.RpcException catch (e) {
throw _RpcErrorCodes.buildRpcException(
_RpcErrorCodes.kExpressionCompilationError,
data: e.data,
);
}
return await _evaluateCompiledExpression(
parameters, isolateId, kernelBase64);
}
Future<Map<String, dynamic>> _buildScope(
json_rpc.Parameters parameters) async {
final params = _setupParams(parameters);
params['isolateId'] = parameters['isolateId'].asString;
if (parameters['scope'].asMapOr(null) != null) {
params['scope'] = parameters['scope'].asMap;
}
return await dds._vmServiceClient.sendRequest(
'_buildExpressionEvaluationScope',
params,
);
}
Future<String> _compileExpression(String isolateId, String expression,
Map<String, dynamic> buildScopeResponseResult) async {
_DartDevelopmentServiceClient externalClient =
dds.clientManager.findFirstClientThatHandlesService(
'compileExpression',
);
final compileParams = <String, dynamic>{
'isolateId': isolateId,
'expression': expression,
'definitions': buildScopeResponseResult['param_names'],
'typeDefinitions': buildScopeResponseResult['type_params_names'],
'libraryUri': buildScopeResponseResult['libraryUri'],
'isStatic': buildScopeResponseResult['isStatic'],
};
final klass = buildScopeResponseResult['klass'];
if (klass != null) {
compileParams['klass'] = klass;
}
// TODO(bkonyi): handle service disappeared case?
try {
if (externalClient != null) {
return (await externalClient.sendRequest(
'compileExpression',
compileParams,
))['result']['kernelBytes'];
} else {
// Fallback to compiling using the kernel service.
return (await dds._vmServiceClient.sendRequest(
'_compileExpression',
compileParams,
))['kernelBytes'];
}
} on json_rpc.RpcException catch (e) {
throw _RpcErrorCodes.buildRpcException(
_RpcErrorCodes.kExpressionCompilationError,
data: e.data,
);
}
}
Future<Map<String, dynamic>> _evaluateCompiledExpression(
json_rpc.Parameters parameters,
String isolateId,
String kernelBase64,
) async {
final params = _setupParams(parameters);
params['isolateId'] = isolateId;
params['kernelBytes'] = kernelBase64;
params['disableBreakpoints'] =
parameters['disableBreakpoints'].asBoolOr(false);
if (parameters['scope'].asMapOr(null) != null) {
params['scope'] = parameters['scope'].asMap;
}
return await dds._vmServiceClient.sendRequest(
'_evaluateCompiledExpression',
params,
);
}
Map<String, dynamic> _setupParams(json_rpc.Parameters parameters) {
if (parameters.method == 'evaluateInFrame') {
return <String, dynamic>{
'frameIndex': parameters['frameIndex'].asInt,
};
} else {
assert(parameters.method == 'evaluate');
return <String, dynamic>{
'targetId': parameters['targetId'].asString,
};
}
}
final _DartDevelopmentService dds;
}

View file

@ -5,10 +5,11 @@
part of dds;
abstract class _RpcErrorCodes {
static json_rpc.RpcException buildRpcException(int code) {
static json_rpc.RpcException buildRpcException(int code, {dynamic data}) {
return json_rpc.RpcException(
code,
errorMessages[code],
data: data,
);
}
@ -34,7 +35,7 @@ abstract class _RpcErrorCodes {
// static const kIsolateMustHaveReloaded = 110;
static const kServiceAlreadyRegistered = 111;
static const kServiceDisappeared = 112;
// static const kExpressionCompilationError = 113;
static const kExpressionCompilationError = 113;
// static const kInvalidTimelineRequest = 114;
// Experimental (used in private rpcs).
@ -48,5 +49,6 @@ abstract class _RpcErrorCodes {
kStreamNotSubscribed: 'Stream not subscribed',
kServiceAlreadyRegistered: 'Service already registered',
kServiceDisappeared: 'Service has disappeared',
kExpressionCompilationError: 'Expression compilation error',
};
}

View file

@ -3,7 +3,7 @@ description: >-
A library used to spawn the Dart Developer Service, used to communicate with
a Dart VM Service instance.
version: 1.2.0
version: 1.2.1
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds

View file

@ -65,7 +65,6 @@ Future testStrings(Isolate isolate) async {
expectTruncatedString(String varName, String varValueAsString) {
Field field = lib.variables.singleWhere((v) => v.name == varName);
Instance value = field.staticValue;
print(value.valueAsString);
expect(varValueAsString, startsWith(value.valueAsString));
expect(value.valueAsStringIsTruncated, isTrue);
}

View file

@ -174,7 +174,6 @@ void JSONStream::PrintError(intptr_t code, const char* details_format, ...) {
va_start(args2, details_format);
Utils::VSNPrint(buffer, (len + 1), details_format, args2);
va_end(args2);
data.AddProperty("details", buffer);
}
}

View file

@ -38,6 +38,7 @@ class Zone;
//
// - runtime/vm/service/vmservice.dart
// - runtime/observatory/lib/src/service/object.dart
// - pkg/dds/lib/src/rpc_error_codes.dart
//
enum JSONRpcErrorCode {
kParseError = -32700,

View file

@ -362,7 +362,7 @@ class Server {
});
} else {
// Forward the websocket connection request to DDS.
request.response.redirect(_service.ddsUri);
request.response.redirect(_service.ddsUri!);
}
return;
}

View file

@ -75,6 +75,14 @@ class _Evaluator {
_Evaluator(this._message, this._isolate, this._service);
Future<Response> run() async {
if (_service.ddsUri != null) {
return Response.from(encodeRpcError(
_message,
kInternalError,
details: 'Fell through to VM Service expression evaluation when a DDS '
'instance was connected. Please file an issue on GitHub.',
));
}
final buildScopeResponse = await _buildScope();
final responseJson = buildScopeResponse.decodeJson();

View file

@ -47,7 +47,7 @@ final serviceAuthToken = _makeAuthToken();
final isolateEmbedderData = <int, IsolateEmbedderData>{};
// These must be kept in sync with the declarations in vm/json_stream.h and
// pkg/dds/lib/src/stream_manager.dart.
// pkg/dds/lib/src/rpc_error_codes.dart.
const kParseError = -32700;
const kInvalidRequest = -32600;
const kMethodNotFound = -32601;
@ -219,7 +219,7 @@ class VMService extends MessageRouter {
final devfs = DevFS();
Uri get ddsUri => _ddsUri!;
Uri? get ddsUri => _ddsUri;
Uri? _ddsUri;
Future<String> _yieldControlToDDS(Message message) async {