Skip to content

Commit 2cad786

Browse files
author
Anna Gringauze
authored
Cleanup getObject logic for handling offsets and counts (#1936)
* Validate only needed summaries in expression_compiler_service * Cleanup getObject code with offsets and counts * Update comments * Addressed CR comments * Addressed CR comments * Fix incorrect range count * Added missing test cases
1 parent b399e94 commit 2cad786

File tree

10 files changed

+259
-83
lines changed

10 files changed

+259
-83
lines changed

dwds/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 17.0.1-dev
2+
3+
- Cleanup `getObject` code for lists and maps.
4+
- Now works with offset `0` and `null` count.
5+
16
## 17.0.0
27

38
- Include debug information in the event sent from the injected client to the

dwds/lib/src/debugging/debugger.dart

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -402,22 +402,41 @@ class Debugger extends Domain {
402402
return null;
403403
}
404404

405+
static bool _isSubRange({int? offset, int? count, int? length}) {
406+
if (length == null) return false;
407+
if (offset == 0 && count == null) return false;
408+
return offset != null || count != null;
409+
}
410+
411+
/// Compute the last possible element index in the range of [offset]..end
412+
/// that includes [count] elements, if available.
413+
static int? _calculateRangeEnd(
414+
{int? count, required int offset, required int length}) =>
415+
count == null ? null : math.min(offset + count, length);
416+
417+
/// Calculate the number of available elements in the range.
418+
static int _calculateRangeCount(
419+
{int? count, required int offset, required int length}) =>
420+
count == null ? length - offset : math.min(count, length - offset);
421+
405422
/// Find a sub-range of the entries for a Map/List when offset and/or count
406423
/// have been specified on a getObject request.
407424
///
408425
/// If the object referenced by [id] is not a system List or Map then this
409426
/// will just return a RemoteObject for it and ignore [offset], [count] and
410427
/// [length]. If it is, then [length] should be the number of entries in the
411428
/// List/Map and [offset] and [count] should indicate the desired range.
412-
Future<RemoteObject> _subrange(
413-
String id, int offset, int? count, int length) async {
429+
Future<RemoteObject> _subRange(String id,
430+
{required int offset, int? count, required int length}) async {
414431
// TODO(#809): Sometimes we already know the type of the object, and
415432
// we could take advantage of that to short-circuit.
416433
final receiver = remoteObjectFor(id);
417-
final end = count == null ? null : math.min(offset + count, length);
418-
final actualCount = count ?? length - offset;
434+
final end =
435+
_calculateRangeEnd(count: count, offset: offset, length: length);
436+
final rangeCount =
437+
_calculateRangeCount(count: count, offset: offset, length: length);
419438
final args =
420-
[offset, actualCount, end].map(dartIdFor).map(remoteObjectFor).toList();
439+
[offset, rangeCount, end].map(dartIdFor).map(remoteObjectFor).toList();
421440
// If this is a List, just call sublist. If it's a Map, get the entries, but
422441
// avoid doing a toList on a large map using skip/take to get the section we
423442
// want. To make those alternatives easier in JS, pass both count and end.
@@ -468,11 +487,16 @@ class Debugger extends Domain {
468487
/// Symbol(DartClass.actualName) and will need to be converted. For a system
469488
/// List or Map, [offset] and/or [count] can be provided to indicate a desired
470489
/// range of entries. They will be ignored if there is no [length].
471-
Future<List<Property>> getProperties(String objectId,
472-
{int? offset, int? count, int? length}) async {
490+
Future<List<Property>> getProperties(
491+
String objectId, {
492+
int? offset,
493+
int? count,
494+
int? length,
495+
}) async {
473496
String rangeId = objectId;
474-
if (length != null && (offset != null || count != null)) {
475-
final range = await _subrange(objectId, offset ?? 0, count ?? 0, length);
497+
if (_isSubRange(offset: offset, count: count, length: length)) {
498+
final range = await _subRange(objectId,
499+
offset: offset ?? 0, count: count, length: length!);
476500
rangeId = range.objectId ?? rangeId;
477501
}
478502
final jsProperties = await sendCommandAndValidateResult<List>(

dwds/lib/src/debugging/instance.dart

Lines changed: 74 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -116,16 +116,15 @@ class InstanceHelper extends Domain {
116116
return _closureInstanceFor(remoteObject);
117117
}
118118

119-
final properties = await debugger.getProperties(objectId,
120-
offset: offset, count: count, length: metaData.length);
121119
if (metaData.isSystemList) {
122-
return await _listInstanceFor(
123-
classRef, remoteObject, properties, offset, count);
120+
return await _listInstanceFor(classRef, remoteObject,
121+
offset: offset, count: count, length: metaData.length);
124122
} else if (metaData.isSystemMap) {
125-
return await _mapInstanceFor(
126-
classRef, remoteObject, properties, offset, count);
123+
return await _mapInstanceFor(classRef, remoteObject,
124+
offset: offset, count: count, length: metaData.length);
127125
} else {
128-
return await _plainInstanceFor(classRef, remoteObject, properties);
126+
return await _plainInstanceFor(classRef, remoteObject,
127+
offset: offset, count: count, length: metaData.length);
129128
}
130129
}
131130

@@ -171,10 +170,18 @@ class InstanceHelper extends Domain {
171170

172171
/// Create a plain instance of [classRef] from [remoteObject] and the JS
173172
/// properties [properties].
174-
Future<Instance?> _plainInstanceFor(ClassRef classRef,
175-
RemoteObject remoteObject, List<Property> properties) async {
173+
Future<Instance?> _plainInstanceFor(
174+
ClassRef classRef,
175+
RemoteObject remoteObject, {
176+
int? offset,
177+
int? count,
178+
int? length,
179+
}) async {
176180
final objectId = remoteObject.objectId;
177181
if (objectId == null) return null;
182+
183+
final properties = await debugger.getProperties(objectId,
184+
offset: offset, count: count, length: length);
178185
final dartProperties = await _dartFieldsFor(properties, remoteObject);
179186
var boundFields = await Future.wait(
180187
dartProperties.map<Future<BoundField>>((p) => _fieldFor(p, classRef)));
@@ -200,8 +207,8 @@ class InstanceHelper extends Domain {
200207
}
201208

202209
/// The associations for a Dart Map or IdentityMap.
203-
Future<List<MapAssociation>> _mapAssociations(
204-
RemoteObject map, int? offset, int? count) async {
210+
Future<List<MapAssociation>> _mapAssociations(RemoteObject map,
211+
{int? offset, int? count}) async {
205212
// We do this in in awkward way because we want the keys and values, but we
206213
// can't return things by value or some Dart objects will come back as
207214
// values that we need to be RemoteObject, e.g. a List of int.
@@ -241,67 +248,95 @@ class InstanceHelper extends Domain {
241248

242249
/// Create a Map instance with class [classRef] from [remoteObject].
243250
Future<Instance?> _mapInstanceFor(
244-
ClassRef classRef,
245-
RemoteObject remoteObject,
246-
List<Property> _,
247-
int? offset,
248-
int? count) async {
251+
ClassRef classRef,
252+
RemoteObject remoteObject, {
253+
int? offset,
254+
int? count,
255+
int? length,
256+
}) async {
249257
final objectId = remoteObject.objectId;
250258
if (objectId == null) return null;
259+
251260
// Maps are complicated, do an eval to get keys and values.
252-
final associations = await _mapAssociations(remoteObject, offset, count);
253-
final length = (offset == null && count == null)
254-
? associations.length
255-
: (await instanceRefFor(remoteObject))?.length;
261+
final associations =
262+
await _mapAssociations(remoteObject, offset: offset, count: count);
263+
final rangeCount = _calculateRangeCount(
264+
count: count, elementCount: associations.length, length: length);
256265
return Instance(
257266
identityHashCode: remoteObject.objectId.hashCode,
258267
kind: InstanceKind.kMap,
259268
id: objectId,
260269
classRef: classRef)
261270
..length = length
262271
..offset = offset
263-
..count = (associations.length == length) ? null : associations.length
272+
..count = rangeCount
264273
..associations = associations;
265274
}
266275

267276
/// Create a List instance of [classRef] from [remoteObject] with the JS
268277
/// properties [properties].
269278
Future<Instance?> _listInstanceFor(
270-
ClassRef classRef,
271-
RemoteObject remoteObject,
272-
List<Property> properties,
273-
int? offset,
274-
int? count) async {
279+
ClassRef classRef,
280+
RemoteObject remoteObject, {
281+
int? offset,
282+
int? count,
283+
int? length,
284+
}) async {
275285
final objectId = remoteObject.objectId;
276286
if (objectId == null) return null;
277287

278-
/// TODO(annagrin): split into cases to make the logic clear.
279-
/// TODO(annagrin): make sure we use offset correctly.
280-
final numberOfProperties = _lengthOf(properties) ?? 0;
281-
final length = (offset == null && count == null)
282-
? numberOfProperties
283-
: (await instanceRefFor(remoteObject))?.length;
284-
final indexed = properties.sublist(
285-
0, min(count ?? length ?? numberOfProperties, numberOfProperties));
286-
final fields = await Future.wait(indexed
287-
.map((property) async => await _instanceRefForRemote(property.value)));
288+
final elements = await _listElements(remoteObject,
289+
offset: offset, count: count, length: length);
290+
final rangeCount = _calculateRangeCount(
291+
count: count, elementCount: elements.length, length: length);
288292
return Instance(
289293
identityHashCode: remoteObject.objectId.hashCode,
290294
kind: InstanceKind.kList,
291295
id: objectId,
292296
classRef: classRef)
293297
..length = length
294-
..elements = fields
298+
..elements = elements
295299
..offset = offset
296-
..count = (numberOfProperties == length) ? null : numberOfProperties;
300+
..count = rangeCount;
301+
}
302+
303+
/// The elements for a Dart List.
304+
Future<List<InstanceRef?>> _listElements(
305+
RemoteObject list, {
306+
int? offset,
307+
int? count,
308+
int? length,
309+
}) async {
310+
final properties = await debugger.getProperties(list.objectId!,
311+
offset: offset, count: count, length: length);
312+
final rangeCount = _calculateRangeCount(
313+
count: count, elementCount: _lengthOf(properties), length: length);
314+
final indexed = properties.sublist(0, rangeCount);
315+
316+
return Future.wait(indexed
317+
.map((property) async => await _instanceRefForRemote(property.value)));
318+
}
319+
320+
/// Return the available count of elements in the requested range.
321+
/// Return `null` if the range includes the whole object.
322+
/// [count] is the range length requested by the `getObject` call.
323+
/// [elementCount] is the number of elements in the runtime object.
324+
/// [length] is the expected length of the whole object, read from
325+
/// the [ClassMetaData].
326+
static int? _calculateRangeCount(
327+
{int? count, int? elementCount, int? length}) {
328+
if (count == null) return null;
329+
if (elementCount == null) return null;
330+
if (length == elementCount) return null;
331+
return min(count, elementCount);
297332
}
298333

299334
/// Return the value of the length attribute from [properties], if present.
300335
///
301336
/// This is only applicable to Lists or Maps, where we expect a length
302337
/// attribute. Even if a plain instance happens to have a length field, we
303338
/// don't use it to determine the properties to display.
304-
int? _lengthOf(List<Property> properties) {
339+
static int? _lengthOf(List<Property> properties) {
305340
final lengthProperty = properties.firstWhere((p) => p.name == 'length');
306341
return lengthProperty.value?.value as int?;
307342
}

dwds/lib/src/injected/client.js

Lines changed: 31 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dwds/lib/src/version.dart

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dwds/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: dwds
22
# Every time this changes you need to run `dart run build_runner build`.
3-
version: 17.0.0
3+
version: 17.0.1-dev
44
description: >-
55
A service that proxies between the Chrome debug protocol and the Dart VM
66
service protocol.

0 commit comments

Comments
 (0)