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;
// 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. This is always from the repl.
final context = args.context;
final targetScriptFileUri = context != null &&
context.startsWith('file://') &&
context.endsWith('.dart')
? Uri.tryParse(context)
: null;
/// Clipboard context means the user has chosen to copy the value to the
/// clipboard, so we should strip any quotes and expand to the full string.
final isClipboard = args.context == 'clipboard';
/// In the repl, we should also expand the full string, but keep the quotes
/// because that's our indicator it is a string (eg. "1" vs 1). Since
/// we override context with script IDs for global evaluation, we must
/// also treat presence of targetScriptFileUri as repl.
final isRepl = args.context == 'repl' || targetScriptFileUri != null;
final shouldSuppressQuotes = isClipboard;
final shouldExpandTruncatedValues = isClipboard || isRepl;
if ((thread == null || frameIndex == null) && targetScriptFileUri == null) {
throw DebugAdapterException(
'Evaluation is only supported when the debugger is paused '
'unless you have a Dart file active in the editor');
}
// 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;
var format = expressionData.format ??
// If we didn't parse a format specifier, fall back to the format in
// the arguments.
VariableFormat.fromDapValueFormat(args.format);
if (shouldSuppressQuotes) {
format = format != null
? VariableFormat.from(format, noQuotes: true)
: VariableFormat.noQuotes();
}
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 vmEvaluateInFrame(
thread,
frameIndex,
expression,
);
} 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 vmEvaluate(thread, library.id!, expression);
}
} 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 || shouldExpandTruncatedValues,
format: format,
allowTruncatedValue: !shouldExpandTruncatedValues,
);
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}',
);
}
}