blob: e6b8ca793dc094e2776c4c07e38e3f560f1b9dc2 [file] [log] [blame]
// Copyright (c) 2013, 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 dart._vmservice;
final class _CompileExpressionErrorDetails {
final String details;
_CompileExpressionErrorDetails(this.details);
}
class RunningIsolates implements MessageRouter {
final isolates = <int, RunningIsolate>{};
int? _rootPortId;
RunningIsolates();
void isolateStartup(int portId, SendPort sp, String name) {
if (_rootPortId == null) {
_rootPortId = portId;
}
var ri = RunningIsolate(portId, sp, name);
isolates[portId] = ri;
}
void isolateShutdown(int portId, SendPort sp) {
if (_rootPortId == portId) {
_rootPortId = null;
}
(isolates.remove(portId))?.onIsolateExit();
}
@override
Future<Response> routeRequest(VMService service, Message message) {
String isolateParam = message.params['isolateId']! as String;
int isolateId;
if (!isolateParam.startsWith('isolates/')) {
message.setErrorResponse(
kInvalidParams,
"invalid 'isolateId' parameter: $isolateParam",
);
return message.response;
}
isolateParam = isolateParam.substring('isolates/'.length);
if (isolateParam == 'root') {
isolateId = _rootPortId!;
} else {
try {
isolateId = int.parse(isolateParam);
} catch (e) {
message.setErrorResponse(
kInvalidParams,
"invalid 'isolateId' parameter: $isolateParam",
);
return message.response;
}
}
final isolate = isolates[isolateId];
if (isolate == null) {
// There is some chance that this isolate may have lived before,
// so return a sentinel rather than an error.
final result = <String, String>{
'type': 'Sentinel',
'kind': 'Collected',
'valueAsString': '<collected>',
};
message.setResponse(encodeResult(message, result));
return message.response;
}
if (message.method == 'evaluateInFrame' || message.method == 'evaluate') {
return _Evaluator(message, isolate, service).run();
} else {
return isolate.routeRequest(service, message);
}
}
@override
void routeResponse(Message message) {}
}
// NOTE: The following class is a duplicate of one in
// 'package:frontend_server/resident_frontend_server_utils.dart'. We are forced
// to duplicate it because `dart:_vmservice` is not allowed to import
// `package:frontend_server`.
final class _ResidentCompilerInfo {
final String? _sdkHash;
final InternetAddress _address;
final int _port;
/// The SDK hash that kernel files compiled using the Resident Frontend
/// Compiler associated with this object will be stamped with.
String? get sdkHash => _sdkHash;
/// The address that the Resident Frontend Compiler associated with this
/// object is listening from.
InternetAddress get address => _address;
/// The port number that the Resident Frontend Compiler associated with this
/// object is listening on.
int get port => _port;
/// Extracts the value associated with a key from [entries], where [entries]
/// is a [String] with the format '$key1:$value1 $key2:$value2 $key3:$value3 ...'.
static String _extractValueAssociatedWithKey(String entries, String key) =>
RegExp(
'$key:'
r'(\S+)(\s|$)',
).allMatches(entries).first[1]!;
static _ResidentCompilerInfo fromFile(File file) {
final fileContents = file.readAsStringSync();
return _ResidentCompilerInfo._(
sdkHash:
fileContents.contains('sdkHash:')
? _extractValueAssociatedWithKey(fileContents, 'sdkHash')
: null,
address: InternetAddress(
_extractValueAssociatedWithKey(fileContents, 'address'),
),
port: int.parse(_extractValueAssociatedWithKey(fileContents, 'port')),
);
}
_ResidentCompilerInfo._({
required String? sdkHash,
required int port,
required InternetAddress address,
}) : _sdkHash = sdkHash,
_port = port,
_address = address;
}
/// Class that knows how to orchestrate expression evaluation in dart2 world.
class _Evaluator {
_Evaluator(this._message, this._isolate, this._service);
Future<Response> run() async {
final buildScopeResponse = await _buildScope();
final responseJson =
buildScopeResponse.decodeJson() as Map<String, dynamic>;
if (responseJson.containsKey('error')) {
final error = responseJson['error'] as Map<String, dynamic>;
final data = error['data'] as Map<String, dynamic>;
return Response.from(
encodeCompilationError(_message, data['details'] as String),
);
}
String kernelBase64;
try {
kernelBase64 = await _compileExpression(
responseJson['result'] as Map<String, dynamic>,
);
} on _CompileExpressionErrorDetails catch (e) {
return Response.from(
encodeRpcError(
_message,
kExpressionCompilationError,
details: e.details,
),
);
}
return await _evaluateCompiledExpression(kernelBase64);
}
Message _message;
RunningIsolate _isolate;
VMService _service;
Future<Response> _buildScope() {
final params = _setupParams();
params['isolateId'] = _message.params['isolateId'];
final buildScopeParams = <String, dynamic>{
'method': '_buildExpressionEvaluationScope',
'id': _message.serial,
'params': params,
};
if (_message.params['scope'] != null) {
(buildScopeParams['params'] as Map<String, dynamic>)['scope'] =
_message.params['scope'];
}
final buildScope = Message._fromJsonRpcRequest(
_message.client!,
buildScopeParams,
);
// Decode the JSON and insert it into the map. The map key
// is the request Uri.
return _isolate.routeRequest(_service, buildScope);
}
/// If [response] represents a valid JSON-RPC result, then this function
/// returns the 'kernelBytes' property of that result. Otherwise, this
/// function throws a [_CompileExpressionErrorDetails] object wrapping the
/// 'details' property of the JSON-RPC error.
static String _getKernelBytesOrThrowErrorDetails(
Map<String, dynamic> response,
) {
if (response['result'] != null) {
return (response['result'] as Map<String, dynamic>)['kernelBytes']
as String;
}
final error = response['error'] as Map<String, dynamic>;
final data = error['data'] as Map<String, dynamic>;
throw _CompileExpressionErrorDetails(data['details']);
}
// NOTE: The following function is a duplicate of one in
// 'package:frontend_server/resident_frontend_server_utils.dart'. We are
// forced to duplicate it because `dart:_vmservice` is not allowed to import
// `package:frontend_server`.
/// Sends a compilation [request] to the resident frontend compiler associated
/// with [serverInfoFile], and returns the compiler's JSON response.
///
/// Throws a [FileSystemException] if [serverInfoFile] cannot be accessed.
static Future<Map<String, dynamic>>
_sendRequestToResidentFrontendCompilerAndRecieveResponse(
String request,
File serverInfoFile,
) async {
Socket? client;
Map<String, dynamic> jsonResponse;
final residentCompilerInfo = _ResidentCompilerInfo.fromFile(serverInfoFile);
try {
client = await Socket.connect(
residentCompilerInfo.address,
residentCompilerInfo.port,
);
client.write(request);
final data = String.fromCharCodes(await client.first);
jsonResponse = jsonDecode(data);
} catch (e) {
jsonResponse = <String, dynamic>{
'success': false,
'errorMessage': e.toString(),
};
}
client?.destroy();
return jsonResponse;
}
/// If compilation fails, this method will throw a
/// [_CompileExpressionErrorDetails] object that will be used to populate the
/// 'details' field of the response to the evaluation RPC that requested this
/// compilation to happen.
Future<String> _compileExpression(
Map<String, dynamic> buildScopeResponseResult,
) async {
Client? externalClient = _service._findFirstClientThatHandlesService(
'compileExpression',
);
final compileParams = <String, dynamic>{
'isolateId': _message.params['isolateId']!,
'expression': _message.params['expression']!,
'definitions': buildScopeResponseResult['param_names']!,
'definitionTypes': buildScopeResponseResult['param_types']!,
'typeDefinitions': buildScopeResponseResult['type_params_names']!,
'typeBounds': buildScopeResponseResult['type_params_bounds']!,
'typeDefaults': buildScopeResponseResult['type_params_defaults']!,
'libraryUri': buildScopeResponseResult['libraryUri']!,
'tokenPos': buildScopeResponseResult['tokenPos']!,
'isStatic': buildScopeResponseResult['isStatic']!,
};
final klass = buildScopeResponseResult['klass'];
if (klass != null) {
compileParams['klass'] = klass;
}
final scriptUri = buildScopeResponseResult['scriptUri'];
if (scriptUri != null) {
compileParams['scriptUri'] = scriptUri;
}
final method = buildScopeResponseResult['method'];
if (method != null) {
compileParams['method'] = method;
}
if (externalClient != null) {
// Let the external client handle expression compilation.
final compileExpression = Message.forMethod('compileExpression');
compileExpression.client = externalClient;
compileExpression.params.addAll(compileParams);
final id = _service._serviceRequests.newId();
final oldId = _message.serial;
final completer = Completer<String>();
externalClient.serviceHandles[id] = (Message? m) {
if (m != null) {
completer.complete(json.encode(m.forwardToJson({'id': oldId})));
} else {
completer.complete(encodeRpcError(_message, kServiceDisappeared));
}
};
externalClient.post(
Response.json(
compileExpression.forwardToJson({
'id': id,
'method': 'compileExpression',
}),
),
);
return completer.future
.then((s) => jsonDecode(s))
.then(
(json) => _getKernelBytesOrThrowErrorDetails(
json as Map<String, dynamic>,
),
);
} else if (VMServiceEmbedderHooks.getResidentCompilerInfoFile!() != null) {
// Compile the expression using the resident compiler.
final response =
await _sendRequestToResidentFrontendCompilerAndRecieveResponse(
jsonEncode({
'command': 'compileExpression',
'expression': compileParams['expression'],
'definitions': compileParams['definitions'],
'definitionTypes': compileParams['definitionTypes'],
'typeDefinitions': compileParams['typeDefinitions'],
'typeBounds': compileParams['typeBounds'],
'typeDefaults': compileParams['typeDefaults'],
'libraryUri': compileParams['libraryUri'],
'offset': compileParams['tokenPos'],
'isStatic': compileParams['isStatic'],
'class': compileParams['klass'],
'scriptUri': compileParams['scriptUri'],
'method': compileParams['method'],
'rootLibraryUri': buildScopeResponseResult['rootLibraryUri'],
}),
VMServiceEmbedderHooks.getResidentCompilerInfoFile!()!,
);
if (response['success'] == true) {
return response['kernelBytes'];
} else if (response['errorMessage'] != null) {
throw _CompileExpressionErrorDetails(response['errorMessage']);
} else {
final compilerOutputLines =
(response['compilerOutputLines'] as List<dynamic>).cast<String>();
throw _CompileExpressionErrorDetails(compilerOutputLines.join('\n'));
}
} else {
// fallback to compile using kernel service
final compileExpressionParams = <String, dynamic>{
'method': '_compileExpression',
'id': _message.serial,
'params': compileParams,
};
final compileExpression = Message._fromJsonRpcRequest(
_message.client!,
compileExpressionParams,
);
return _isolate
.routeRequest(_service, compileExpression)
.then((response) => response.decodeJson())
.then(
(json) => _getKernelBytesOrThrowErrorDetails(
json as Map<String, dynamic>,
),
);
}
}
Future<Response> _evaluateCompiledExpression(String kernelBase64) {
if (kernelBase64.isNotEmpty) {
final params = _setupParams();
params['isolateId'] = _message.params['isolateId'];
params['kernelBytes'] = kernelBase64;
params['disableBreakpoints'] = _message.params['disableBreakpoints'];
final runParams = <String, dynamic>{
'method': '_evaluateCompiledExpression',
'id': _message.serial,
'params': params,
};
if (_message.params['scope'] != null) {
(runParams['params'] as Map<String, dynamic>)['scope'] =
_message.params['scope'];
}
if (_message.params['idZoneId'] != null) {
(runParams['params'] as Map<String, dynamic>)['idZoneId'] =
_message.params['idZoneId'];
}
final runExpression = Message._fromJsonRpcRequest(
_message.client!,
runParams,
);
return _isolate.routeRequest(_service, runExpression); // _message
} else {
// empty kernel indicates dart1 mode
return _isolate.routeRequest(_service, _message);
}
}
Map<String, dynamic> _setupParams() {
if (_message.method == 'evaluateInFrame') {
return <String, dynamic>{'frameIndex': _message.params['frameIndex']};
} else {
assert(_message.method == 'evaluate');
return <String, dynamic>{'targetId': _message.params['targetId']};
}
}
}