evaluateRequest method
evaluateRequest is called by the client to evaluate a string expression.
This could come from the user typing into an input (for example VS Code's Debug Console), automatic refresh of a Watch window, or called as part of an operation like "Copy Value" for an item in the watch/variables window.
If execution is not paused, the frameId
will not be provided.
Implementation
@override
Future<void> evaluateRequest(
Request request,
EvaluateArguments args,
void Function(EvaluateResponseBody) sendResponse,
) async {
final frameId = args.frameId;
// TODO(dantup): Special handling for clipboard/watch (see Dart-Code DAP) to
// avoid wrapping strings in quotes, etc.
// If the frameId was supplied, it maps to an ID we provided from stored
// data so we need to look up the isolate + frame index for it.
ThreadInfo? thread;
int? frameIndex;
if (frameId != null) {
final data = isolateManager.getStoredData(frameId);
if (data != null) {
thread = data.thread;
frameIndex = (data.data as vm.Frame).index;
}
}
// To support global evaluation, we allow passing a file:/// URI in the
// context argument.
final context = args.context;
final targetScriptFileUri = context != null &&
context.startsWith('file://') &&
context.endsWith('.dart')
? Uri.tryParse(context)
: null;
if ((thread == null || frameIndex == null) && targetScriptFileUri == null) {
throw UnimplementedError(
'Global evaluation not currently supported without a Dart script context');
}
// Parse the expression for trailing format specifiers.
final expressionData = EvaluationExpression.parse(
args.expression
.trim()
// Remove any trailing semicolon as the VM only evaluates expressions
// but a user may have highlighted a whole line/statement to send for
// evaluation.
.replaceFirst(_trailingSemicolonPattern, ''),
);
final expression = expressionData.expression;
final format = expressionData.format ??
// If we didn't parse a format specifier, fall back to the format in
// the arguments.
VariableFormat.fromDapValueFormat(args.format);
final exceptionReference = thread?.exceptionReference;
// The value in the constant `frameExceptionExpression` is used as a special
// expression that evaluates to the exception on the current thread. This
// allows us to construct evaluateNames that evaluate to the fields down the
// tree to support some of the debugger functionality (for example
// "Copy Value", which re-evaluates).
final isExceptionExpression = expression == threadExceptionExpression ||
expression.startsWith('$threadExceptionExpression.');
vm.Response? result;
try {
if (thread != null &&
exceptionReference != null &&
isExceptionExpression) {
result = await _evaluateExceptionExpression(
exceptionReference,
expression,
thread,
);
} else if (thread != null && frameIndex != null) {
result = await vmService?.evaluateInFrame(
thread.isolate.id!,
frameIndex,
expression,
disableBreakpoints: true,
);
} else if (targetScriptFileUri != null &&
// Since we can't currently get a thread, we assume the first thread is
// a reasonable target for global evaluation.
(thread = isolateManager.threads.firstOrNull) != null &&
thread != null) {
final library = await thread.getLibraryForFileUri(targetScriptFileUri);
if (library == null) {
// Wrapped in DebugAdapterException in the catch below.
throw 'Unable to find the library for $targetScriptFileUri';
}
result = await vmService?.evaluate(
thread.isolate.id!,
library.id!,
expression,
disableBreakpoints: true,
);
}
} catch (e) {
final rawMessage = '$e';
// Error messages can be quite verbose and don't fit well into a
// single-line watch window. For example:
//
// evaluateInFrame: (113) Expression compilation error
// org-dartlang-debug:synthetic_debug_expression:1:5: Error: A value of type 'String' can't be assigned to a variable of type 'num'.
// 1 + "a"
// ^
//
// So in the case of a Watch context, try to extract the useful message.
if (args.context == 'watch') {
throw DebugAdapterException(extractEvaluationErrorMessage(rawMessage));
}
throw DebugAdapterException(rawMessage);
}
if (result is vm.ErrorRef) {
throw DebugAdapterException(result.message ?? '<error ref>');
} else if (result is vm.Sentinel) {
throw DebugAdapterException(result.valueAsString ?? '<collected>');
} else if (result is vm.InstanceRef && thread != null) {
final resultString = await _converter.convertVmInstanceRefToDisplayString(
thread,
result,
allowCallingToString: evaluateToStringInDebugViews,
format: format,
);
final variablesReference = _converter.isSimpleKind(result.kind)
? 0
: thread.storeData(VariableData(result, format));
// Store the expression that gets this object as we may need it to
// compute evaluateNames for child objects later.
storeEvaluateName(result, expression);
sendResponse(EvaluateResponseBody(
result: resultString,
variablesReference: variablesReference,
));
} else {
throw DebugAdapterException(
'Unknown evaluation response type: ${result?.runtimeType}',
);
}
}