evaluateRequest method

  1. @override
Future<void> evaluateRequest(
  1. Request request,
  2. EvaluateArguments args,
  3. void sendResponse(
    1. EvaluateResponseBody
    )
)

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 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 || 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}',
    );
  }
}