dart-sdk/sdk_nnbd/lib/vmservice/running_isolate.dart
Alexander Aprelev 305399c15c [vm/vmservice] Validate getIsolate response before attempting to retrieve pauseEvent from it.
Bug: b/150193047
Change-Id: I6e8f8e27ef2a9894138b1d877597ad3a5f3f9e61
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/137281
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
2020-02-25 23:19:44 +00:00

134 lines
4.6 KiB
Dart

// 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;
class RunningIsolate implements MessageRouter {
final int portId;
final SendPort sendPort;
final String name;
final Set<String> _resumeApprovalsByName = {};
RunningIsolate(this.portId, this.sendPort, this.name);
String get serviceId => 'isolates/$portId';
static const kInvalidPauseEvent = -1;
static const kPauseOnStartMask = 1 << 0;
static const kPauseOnReloadMask = 1 << 1;
static const kPauseOnExitMask = 1 << 2;
static const kDefaultResumePermissionMask =
kPauseOnStartMask | kPauseOnReloadMask | kPauseOnExitMask;
/// Resumes the isolate if all clients which need to approve a resume have
/// done so. Called when the last client of a given name disconnects or
/// changes name to ensure we don't deadlock waiting for approval to resume
/// from a disconnected client.
Future<void> maybeResumeAfterClientChange(
VMService service, String disconnectedClientName) async {
// Remove approvals from the disconnected client.
_resumeApprovalsByName.remove(disconnectedClientName);
// If we've received approval to resume from all clients who care, clear
// approval state and resume.
var pauseType;
try {
pauseType = await _isolatePauseType(service, portId.toString());
} catch (_errorResponse) {
// ignore errors when attempting to retrieve isolate pause type
return;
}
if (pauseType != kInvalidPauseEvent &&
_shouldResume(service, null, pauseType)) {
_resumeApprovalsByName.clear();
await Message.forMethod('resume')
..params.addAll({
'isolateId': portId,
})
..sendToIsolate(sendPort);
}
}
bool _shouldResume(VMService service, Client? client, int pauseType) {
if (client != null) {
// Mark the approval by the client.
_resumeApprovalsByName.add(client.name);
}
final requiredClientApprovals = <String>{};
final permissions = service.clientResumePermissions;
// Determine which clients require approval for this pause type.
permissions.forEach((name, clientNamePermissions) {
if (clientNamePermissions.permissionsMask & pauseType != 0) {
requiredClientApprovals.add(name);
}
});
// We require at least a single client to resume, even if that client
// doesn't require resume approval.
if (_resumeApprovalsByName.isEmpty) {
return false;
}
// If all the required approvals are present, we should resume.
return _resumeApprovalsByName.containsAll(requiredClientApprovals);
}
Future<int> _isolatePauseType(VMService service, String isolateId) async {
final getIsolateMessage = Message.forMethod('getIsolate')
..params.addAll({
'isolateId': isolateId,
});
final Response result = await routeRequest(service, getIsolateMessage);
final resultJson = result.decodeJson();
if (resultJson['result'] == null ||
resultJson['result']['pauseEvent'] == null) {
// Failed to send getIsolate message(due to isolate being de-registered
// for example).
throw result;
}
final pauseEvent = resultJson['result']['pauseEvent'];
const pauseEvents = <String, int>{
'PauseStart': kPauseOnStartMask,
'PausePostRequest': kPauseOnReloadMask,
'PauseExit': kPauseOnExitMask,
};
final kind = pauseEvent['kind'];
return pauseEvents[kind] ?? kInvalidPauseEvent;
}
Future<Response> _routeResumeRequest(
VMService service, Message message) async {
// If we've received approval to resume from all clients who care, clear
// approval state and resume.
var pauseType;
try {
pauseType = await _isolatePauseType(service, message.params['isolateId']);
} catch (errorResponse) {
return errorResponse;
}
if (pauseType == kInvalidPauseEvent ||
_shouldResume(service, message.client, pauseType)) {
_resumeApprovalsByName.clear();
return message.sendToIsolate(sendPort);
}
// We're still awaiting some approvals. Simply return success, but don't
// resume yet.
return Response(ResponsePayloadKind.String, encodeSuccess(message));
}
@override
Future<Response> routeRequest(VMService service, Message message) {
if (message.method == 'resume') {
return _routeResumeRequest(service, message);
}
// Send message to isolate.
return message.sendToIsolate(sendPort);
}
@override
void routeResponse(Message message) {}
}