variablesRequest method
variablesRequest is called by the client to request child variables for a given variables variablesReference.
The variablesReference provided by the client will be a reference the server has previously provided, for example in response to a scopesRequest or an evaluateRequest.
We use the reference to look up the stored data and then create variables based on the type of data. For a Frame, we will return the local variables, for a List/MapAssociation we will return items from it, and for an instance we will return the fields (and possibly getters) for that instance.
Implementation
@override
Future<void> variablesRequest(
Request request,
VariablesArguments args,
void Function(VariablesResponseBody) sendResponse,
) async {
final service = vmService;
final childStart = args.start;
final childCount = args.count;
final storedData = isolateManager.getStoredData(args.variablesReference);
if (storedData == null) {
throw StateError('variablesReference is no longer valid');
}
final thread = storedData.thread;
var data = storedData.data;
VariableFormat? format;
// Unwrap any variable we stored with formatting info.
if (data is VariableData) {
format = data.format;
data = data.data;
}
// If no explicit formatting, use from args.
format ??= VariableFormat.fromDapValueFormat(args.format);
final variables = <Variable>[];
if (data is FrameScopeData && data.kind == FrameScopeDataKind.locals) {
final vars = data.frame.vars;
if (vars != null) {
Future<Variable> convert(int index, vm.BoundVariable variable) {
// Store the expression that gets this object as we may need it to
// compute evaluateNames for child objects later.
final value = variable.value;
if (value is vm.InstanceRef) {
storeEvaluateName(value, variable.name);
}
return _converter.convertVmResponseToVariable(
thread,
variable.value,
name: variable.name,
allowCallingToString: evaluateToStringInDebugViews &&
index < maxToStringsPerEvaluation,
evaluateName: variable.name,
format: format,
);
}
variables.addAll(await Future.wait(vars.mapIndexed(convert)));
// Sort the variables by name.
variables.sortBy((v) => v.name);
}
} else if (data is FrameScopeData &&
data.kind == FrameScopeDataKind.globals) {
/// Helper to simplify calling converter.
Future<Variable> convert(int index, vm.FieldRef fieldRef) async {
return _converter.convertFieldRefToVariable(
thread,
fieldRef,
allowCallingToString:
evaluateToStringInDebugViews && index < maxToStringsPerEvaluation,
format: format,
);
}
final globals = await _getFrameGlobals(thread, data.frame);
variables.addAll(await Future.wait(globals.mapIndexed(convert)));
variables.sortBy((v) => v.name);
} else if (data is InspectData) {
// When sending variables as part of an OutputEvent, VS Code will only
// show the first field, so we wrap the object to ensure there's always
// a single field.
final instance = data.instance;
variables.add(Variable(
name: '', // Unused.
value: '<inspected variable>', // Shown to user, expandable.
variablesReference: instance != null ? thread.storeData(instance) : 0,
));
} else if (data is WrappedInstanceVariable) {
// WrappedInstanceVariables are used to support DAP-over-DDS clients that
// had a VM Instance ID and wanted to convert it to a variable for use in
// `variables` requests.
try {
final response = await isolateManager.getObject(
storedData.thread.isolate,
vm.ObjRef(id: data.instanceId),
offset: childStart,
count: childCount,
);
// Because `variables` requests are a request for _child_ variables but we
// want DAP-over-DDS clients to be able to get the whole variable (eg.
// including toe initial string representation of the variable itself) the
// initial request will return a list containing a single variable named
// `value`. This will contain both the `variablesReference` to get the
// children, and also a `value` field with the display string.
final variable = await _converter.convertVmResponseToVariable(
thread,
response,
name: 'value',
evaluateName: null,
allowCallingToString: evaluateToStringInDebugViews,
);
variables.add(variable);
} on vm.SentinelException catch (e) {
variables.add(Variable(
name: 'value',
value: e.sentinel.valueAsString ?? '<sentinel>',
variablesReference: 0,
));
}
} else if (data is vm.MapAssociation) {
final key = data.key;
final value = data.value;
if (key is vm.InstanceRef && value is vm.InstanceRef) {
// For a MapAssociation, we create a dummy set of variables for "key" and
// "value" so that each may be expanded if they are complex values.
variables.addAll([
Variable(
name: 'key',
value: await _converter.convertVmInstanceRefToDisplayString(
thread,
key,
allowCallingToString: evaluateToStringInDebugViews,
format: format,
),
variablesReference: _converter.isSimpleKind(key.kind)
? 0
: thread.storeData(VariableData(key, format)),
),
Variable(
name: 'value',
value: await _converter.convertVmInstanceRefToDisplayString(
thread,
value,
allowCallingToString: evaluateToStringInDebugViews,
format: format,
),
variablesReference: _converter.isSimpleKind(value.kind)
? 0
: thread.storeData(VariableData(value, format)),
evaluateName:
buildEvaluateName('', parentInstanceRefId: value.id)),
]);
}
} else if (data is vm.ObjRef) {
try {
final object = await isolateManager.getObject(
storedData.thread.isolate,
data,
offset: childStart,
count: childCount,
);
if (object is vm.Sentinel) {
variables.add(Variable(
name: '<eval error>',
value: object.valueAsString ?? '<sentinel>',
variablesReference: 0,
));
} else if (object is vm.Instance) {
variables.addAll(await _converter.convertVmInstanceToVariablesList(
thread,
object,
evaluateName: buildEvaluateName('', parentInstanceRefId: data.id),
allowCallingToString: evaluateToStringInDebugViews,
startItem: childStart,
numItems: childCount,
format: format,
));
} else {
variables.add(Variable(
name: '<eval error>',
value: object.runtimeType.toString(),
variablesReference: 0,
));
}
} on vm.SentinelException catch (e) {
variables.add(Variable(
name: '<eval error>',
value: e.sentinel.valueAsString ?? '<sentinel>',
variablesReference: 0,
));
}
} else if (data is VariableGetter && service != null) {
final variable = await _converter.createVariableForGetter(
service,
thread,
data.instance,
// Empty names for lazy variable values because they were already shown
// in the parent object.
variableName: '',
getterName: data.getterName,
evaluateName: data.parentEvaluateName,
allowCallingToString: data.allowCallingToString,
format: format,
);
variables.add(variable);
}
sendResponse(VariablesResponseBody(variables: variables));
}