From 606e8d24d041d2f286535e3f37526ff7b038a480 Mon Sep 17 00:00:00 2001 From: Anna Gringauze Date: Mon, 24 Apr 2023 16:22:03 -0700 Subject: [PATCH 01/13] Simplify class metadata --- dwds/lib/src/debugging/classes.dart | 8 +- dwds/lib/src/debugging/instance.dart | 163 +++++++++++----- dwds/lib/src/debugging/libraries.dart | 8 +- dwds/lib/src/debugging/metadata/class.dart | 213 +++++++++++++++------ dwds/test/metadata/class_test.dart | 8 +- fixtures/_testPackageSound/pubspec.yaml | 2 +- 6 files changed, 286 insertions(+), 116 deletions(-) diff --git a/dwds/lib/src/debugging/classes.dart b/dwds/lib/src/debugging/classes.dart index f5a529a93..256240dcb 100644 --- a/dwds/lib/src/debugging/classes.dart +++ b/dwds/lib/src/debugging/classes.dart @@ -199,8 +199,12 @@ class ClassHelper extends Domain { fieldDescriptors.forEach((name, descriptor) { final classMetaData = ClassMetaData( jsName: descriptor['classRefName'], - libraryId: descriptor['classRefLibraryId'], - dartName: descriptor['classRefDartName'], + //libraryId: descriptor['classRefLibraryId'], + //dartName: descriptor['classRefDartName'], + classRef: classRefFor( + descriptor['classRefLibraryId'], + descriptor['classRefDartName'], + ), ); fieldRefs.add( FieldRef( diff --git a/dwds/lib/src/debugging/instance.dart b/dwds/lib/src/debugging/instance.dart index 8f43172b5..c6d9b26b9 100644 --- a/dwds/lib/src/debugging/instance.dart +++ b/dwds/lib/src/debugging/instance.dart @@ -127,6 +127,60 @@ class InstanceHelper extends Domain { final classRef = metaData?.classRef; if (metaData == null || classRef == null) return null; + + switch (metaData.kind) { + case InstanceKind.kClosure: + return _closureInstanceFor(remoteObject); + case InstanceKind.kList: + return await _listInstanceFor( + classRef, + remoteObject, + offset: offset, + count: count, + length: metaData.length, + ); + case InstanceKind.kSet: + return await _setInstanceFor( + classRef, + remoteObject, + offset: offset, + count: count, + length: metaData.length, + ); + case InstanceKind.kMap: + return await _mapInstanceFor( + classRef, + remoteObject, + offset: offset, + count: count, + length: metaData.length, + ); + case InstanceKind.kRecord: + return await _recordInstanceFor( + classRef, + remoteObject, + offset: offset, + count: count, + length: metaData.length, + ); + case InstanceKind.kRecordType: + return await _recordTypeInstanceFor( + classRef, + remoteObject, + offset: offset, + count: count, + length: metaData.length, + ); + default: + return await _plainInstanceFor( + classRef, + remoteObject, + offset: offset, + count: count, + length: metaData.length, + ); + } + /* if (metaData.isFunction) { return _closureInstanceFor(remoteObject); } @@ -187,7 +241,7 @@ class InstanceHelper extends Domain { count: count, length: metaData.length, ); - } + }*/ } /// If [remoteObject] represents a primitive, return an [Instance] for it, @@ -868,60 +922,67 @@ class InstanceHelper extends Domain { inspector, ); if (metaData == null) return null; - if (metaData.isSystemList) { - return InstanceRef( - kind: InstanceKind.kList, - id: objectId, - identityHashCode: remoteObject.objectId.hashCode, - classRef: metaData.classRef, - )..length = metaData.length; - } - if (metaData.isSystemMap) { - return InstanceRef( - kind: InstanceKind.kMap, - id: objectId, - identityHashCode: remoteObject.objectId.hashCode, - classRef: metaData.classRef, - )..length = metaData.length; - } - if (metaData.isRecord) { - return InstanceRef( - kind: InstanceKind.kRecord, - id: objectId, - identityHashCode: remoteObject.objectId.hashCode, - classRef: metaData.classRef, - )..length = metaData.length; - } - if (metaData.isRecordType) { - return InstanceRef( - kind: InstanceKind.kRecordType, - id: objectId, - identityHashCode: remoteObject.objectId.hashCode, - classRef: metaData.classRef, - )..length = metaData.length; - } - if (metaData.isSet) { - return InstanceRef( - kind: InstanceKind.kSet, - id: objectId, - identityHashCode: remoteObject.objectId.hashCode, - classRef: metaData.classRef, - )..length = metaData.length; - } - if (metaData.isNativeError) { - return InstanceRef( - kind: InstanceKind.kPlainInstance, - id: objectId, - identityHashCode: remoteObject.objectId.hashCode, - classRef: classRefForNativeJsError, - )..length = metaData.length; - } return InstanceRef( - kind: InstanceKind.kPlainInstance, + kind: metaData.kind, id: objectId, identityHashCode: remoteObject.objectId.hashCode, classRef: metaData.classRef, - ); + )..length = metaData.length; + + // if (metaData.isSystemList) { + // return InstanceRef( + // kind: InstanceKind.kList, + // id: objectId, + // identityHashCode: remoteObject.objectId.hashCode, + // classRef: metaData.classRef, + // )..length = metaData.length; + // } + // if (metaData.isSystemMap) { + // return InstanceRef( + // kind: InstanceKind.kMap, + // id: objectId, + // identityHashCode: remoteObject.objectId.hashCode, + // classRef: metaData.classRef, + // )..length = metaData.length; + // } + // if (metaData.isRecord) { + // return InstanceRef( + // kind: InstanceKind.kRecord, + // id: objectId, + // identityHashCode: remoteObject.objectId.hashCode, + // classRef: metaData.classRef, + // )..length = metaData.length; + // } + // if (metaData.isRecordType) { + // return InstanceRef( + // kind: InstanceKind.kRecordType, + // id: objectId, + // identityHashCode: remoteObject.objectId.hashCode, + // classRef: metaData.classRef, + // )..length = metaData.length; + // } + // if (metaData.isSet) { + // return InstanceRef( + // kind: InstanceKind.kSet, + // id: objectId, + // identityHashCode: remoteObject.objectId.hashCode, + // classRef: metaData.classRef, + // )..length = metaData.length; + // } + // if (metaData.isNativeError) { + // return InstanceRef( + // kind: InstanceKind.kPlainInstance, + // id: objectId, + // identityHashCode: remoteObject.objectId.hashCode, + // classRef: classRefForNativeJsError, + // )..length = metaData.length; + // } + // return InstanceRef( + // kind: InstanceKind.kPlainInstance, + // id: objectId, + // identityHashCode: remoteObject.objectId.hashCode, + // classRef: metaData.classRef, + // ); case 'function': final functionMetaData = await FunctionMetaData.metaDataFor( inspector.remoteDebugger, diff --git a/dwds/lib/src/debugging/libraries.dart b/dwds/lib/src/debugging/libraries.dart index 5b9b96851..d152ced30 100644 --- a/dwds/lib/src/debugging/libraries.dart +++ b/dwds/lib/src/debugging/libraries.dart @@ -120,9 +120,11 @@ class LibraryHelper extends Domain { List>.from(jsonValues['classes'] ?? []); for (final classDescriptor in classDescriptors) { final classMetaData = ClassMetaData( - jsName: classDescriptor['name'] as Object?, - libraryId: libraryRef.id, - dartName: classDescriptor['dartName'] as Object?, + jsName: classDescriptor['name'], + classRef: classRefFor( + libraryRef.id, + classDescriptor['dartName'], + ), ); classRefs.add(classMetaData.classRef); } diff --git a/dwds/lib/src/debugging/metadata/class.dart b/dwds/lib/src/debugging/metadata/class.dart index 1d0c861a8..eca649d3a 100644 --- a/dwds/lib/src/debugging/metadata/class.dart +++ b/dwds/lib/src/debugging/metadata/class.dart @@ -40,6 +40,8 @@ final classRefForUnknown = classRefFor(_dartCoreLibrary, 'Unknown'); final classRefForNativeJsError = classRefFor(_dartInterceptorsLibrary, 'NativeError'); +String classMetaDataIdFor(String library, String? jsName) => '$library:$jsName'; + /// Returns true for non-dart JavaScript classes. /// /// TODO(annagrin): this breaks on name changes for JS types. @@ -69,18 +71,57 @@ LibraryRef libraryRefFor(String libraryId) => LibraryRef( ); /// Returns a [ClassRef] for the provided library ID and class name. -ClassRef classRefFor(String libraryId, String? name) => ClassRef( - id: classIdFor(libraryId, name), - name: name, - library: libraryRefFor(libraryId), - ); +ClassRef classRefFor(Object? libraryId, Object? dartName) { + final library = libraryId as String? ?? _dartCoreLibrary; + final name = dartName as String?; + return ClassRef( + id: classIdFor(library, name), + name: name, + library: libraryRefFor(library), + ); +} String classIdFor(String libraryId, String? name) => 'classes|$libraryId|$name'; +enum ClassMetaDataKind { + object, + map, + set, + list, + function, + record, + recordType, + nativeError, +} + +String toInstanceKind(ClassMetaDataKind kind) { + switch (kind) { + case ClassMetaDataKind.function: + return InstanceKind.kClosure; + case ClassMetaDataKind.list: + return InstanceKind.kList; + case ClassMetaDataKind.map: + return InstanceKind.kMap; + case ClassMetaDataKind.set: + return InstanceKind.kSet; + case ClassMetaDataKind.record: + return InstanceKind.kRecord; + case ClassMetaDataKind.recordType: + return InstanceKind.kRecordType; + case ClassMetaDataKind.nativeError: + case ClassMetaDataKind.object: + default: + return InstanceKind.kPlainInstance; + } +} + /// Meta data for a remote Dart class in Chrome. class ClassMetaData { static final _logger = Logger('ClassMetadata'); + /// Instance kind. + final String kind; + /// Class id. /// /// Takes the form of 'libraryId:name'. @@ -98,44 +139,51 @@ class ClassMetaData { /// The dart type name for the object. /// /// For example, 'int', 'List', 'Null' - final String? dartName; + String? get dartName => classRef.name; /// Class ref for the class metadata. final ClassRef classRef; factory ClassMetaData({ Object? jsName, - Object? libraryId, - Object? dartName, + //Object? libraryId, + //Object? dartName, Object? length, - bool isFunction = false, - bool isRecord = false, - bool isRecordType = false, - bool isNativeError = false, + String kind = InstanceKind.kPlainInstance, + required ClassRef classRef, + //bool isFunction = false, + //bool isRecord = false, + //bool isRecordType = false, + //bool isNativeError = false, }) { - final jName = jsName as String?; - final dName = dartName as String?; - final library = libraryId as String? ?? _dartCoreLibrary; - final id = '$library:$jName'; - - var classRef = classRefFor(library, dName); - if (isRecord) { - classRef = classRefForRecord; - } - if (isRecordType) { - classRef = classRefForRecordType; - } + final id = classMetaDataIdFor(classRef.library!.id!, jsName as String?); + // final jName = jsName as String?; + // final dName = dartName as String?; + //final library = libraryId as String? ?? _dartCoreLibrary; + //final id = '$library:$jName'; + + // var classRef = classRefFor(library, dName); + // if (kind == ClassMetaDataKind.record) { + // classRef = classRefForRecord; + // } + // if (kind == ClassMetaDataKind.recordType) { + // classRef = classRefForRecordType; + // } + // if (kind == ClassMetaDataKind.nativeError) { + // classRef = classRefForNativeJsError; + // } return ClassMetaData._( id, classRef, - jName, - dName, + jsName, + //dartName as String?, int.tryParse('$length'), - isFunction, - isRecord, - isRecordType, - isNativeError, + kind, + // isFunction, + // isRecord, + // isRecordType, + // isNativeError, ); } @@ -143,12 +191,13 @@ class ClassMetaData { this.id, this.classRef, this.jsName, - this.dartName, + //this.dartName, this.length, - this.isFunction, - this.isRecord, - this.isRecordType, - this.isNativeError, + this.kind, + // this.isFunction, + // this.isRecord, + // this.isRecordType, + // this.isNativeError, ); /// Returns the [ClassMetaData] for the Chrome [remoteObject]. @@ -205,14 +254,21 @@ class ClassMetaData { returnByValue: true, ); final metadata = result.value as Map; + final jsName = metadata['name']; + + final kind = _computeInstanceKind(metadata); + final classRef = _computeClassRef(metadata); + return ClassMetaData( - jsName: metadata['name'], - libraryId: metadata['libraryId'], - dartName: metadata['dartName'], - isFunction: metadata['isFunction'], - isRecord: metadata['isRecord'], - isRecordType: metadata['isRecordType'], - isNativeError: metadata['isNativeError'], + jsName: jsName, + //libraryId: library, + //dartName: dartName, + kind: kind, + classRef: classRef, + // isFunction: metadata['isFunction'], + // isRecord: metadata['isRecord'], + // isRecordType: metadata['isRecordType'], + // isNativeError: metadata['isNativeError'], length: metadata['length'], ); } on ChromeDebugException catch (e, s) { @@ -233,23 +289,70 @@ class ClassMetaData { /// plain objects. // TODO(alanknight): It may be that IdentityMap should not be treated as a // system map. - bool get isSystemMap => jsName == 'LinkedMap' || jsName == 'IdentityMap'; + // bool get isSystemMap => jsName == 'LinkedMap' || jsName == 'IdentityMap'; + + // /// True if this class refers to system Lists, which are treated specially. + // bool get isSystemList => jsName == 'JSArray'; - /// True if this class refers to system Lists, which are treated specially. - bool get isSystemList => jsName == 'JSArray'; + // bool get isSet => jsName == '_HashSet'; - bool get isSet => jsName == '_HashSet'; + // /// True if this class refers to a function type. + // bool isFunction; - /// True if this class refers to a function type. - bool isFunction; + // /// True if this class refers to a Record type. + // bool isRecord; - /// True if this class refers to a Record type. - bool isRecord; + // /// True if this class refers to a RecordType type. + // bool isRecordType; - /// True if this class refers to a RecordType type. - bool isRecordType; + // /// True is this class refers to a native JS type. + // /// i.e. inherits from NativeError. + // bool isNativeError; +} + +String _computeInstanceKind(Map metadata) { + final jsName = metadata['name']; + final isFunction = metadata['isFunction']; + final isRecord = metadata['isRecord']; + final isRecordType = metadata['isRecordType']; + + if (jsName == '_HashSet') { + return InstanceKind.kSet; + } + if (jsName == 'JSArray') { + return InstanceKind.kList; + } + if (jsName == 'LinkedMap' || jsName == 'IdentityMap') { + return InstanceKind.kMap; + } + if (isFunction) { + return InstanceKind.kClosure; + } + if (isRecord) { + return InstanceKind.kRecord; + } + if (isRecordType) { + return InstanceKind.kRecordType; + } + return InstanceKind.kPlainInstance; +} + +ClassRef _computeClassRef(Map metadata) { + final dartName = metadata['dartName']; + final library = metadata['libraryId']; + final isRecord = metadata['isRecord']; + final isRecordType = metadata['isRecordType']; + final isNativeError = metadata['isNativeError']; + + if (isRecord) { + return classRefForRecord; + } + if (isRecordType) { + return classRefForRecordType; + } + if (isNativeError) { + return classRefForNativeJsError; + } - /// True is this class refers to a native JS type. - /// i.e. inherits from NativeError. - bool isNativeError; + return classRefFor(library, dartName); } diff --git a/dwds/test/metadata/class_test.dart b/dwds/test/metadata/class_test.dart index 49b4e4b96..d0c765ebe 100644 --- a/dwds/test/metadata/class_test.dart +++ b/dwds/test/metadata/class_test.dart @@ -9,16 +9,16 @@ import 'package:test/test.dart'; void main() { test('Gracefully handles invalid length objects', () async { - var metadata = ClassMetaData(length: null); + var metadata = ClassMetaData(length: null, classRef: classRefForUnknown); expect(metadata.length, isNull); - metadata = ClassMetaData(length: {}); + metadata = ClassMetaData(length: {}, classRef: classRefForUnknown); expect(metadata.length, isNull); - metadata = ClassMetaData(length: '{}'); + metadata = ClassMetaData(length: '{}', classRef: classRefForUnknown); expect(metadata.length, isNull); - metadata = ClassMetaData(length: 0); + metadata = ClassMetaData(length: 0, classRef: classRefForUnknown); expect(metadata.length, equals(0)); }); } diff --git a/fixtures/_testPackageSound/pubspec.yaml b/fixtures/_testPackageSound/pubspec.yaml index 4207b8667..c7d473804 100644 --- a/fixtures/_testPackageSound/pubspec.yaml +++ b/fixtures/_testPackageSound/pubspec.yaml @@ -12,6 +12,6 @@ dependencies: path: ../_testSound dev_dependencies: - build_runner: ^2.4.0 + build_runner: 2.4.0 build_web_compilers: ^4.0.0 From a896519bf1ca31a81032520367af9b4c18cbd72a Mon Sep 17 00:00:00 2001 From: Anna Gringauze Date: Tue, 25 Apr 2023 15:23:51 -0700 Subject: [PATCH 02/13] Cleanup --- dwds/CHANGELOG.md | 1 + dwds/lib/src/debugging/classes.dart | 6 +- dwds/lib/src/debugging/instance.dart | 68 ------------------ dwds/lib/src/debugging/libraries.dart | 1 + dwds/lib/src/debugging/metadata/class.dart | 84 +++------------------- dwds/test/metadata/class_test.dart | 25 +++++-- 6 files changed, 33 insertions(+), 152 deletions(-) diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index 65c501916..dca5e719e 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -3,6 +3,7 @@ - Do not show async frame errors on evaluation. - [#2073](https://github.com/dart-lang/webdev/pull/2073) - Refactor code for presenting record instances. - [#2074](https://github.com/dart-lang/webdev/pull/2074) - Display record types concisely. - [#2070](https://github.com/dart-lang/webdev/pull/2070) +- Simplify class metadata. - [#2070](https://github.com/dart-lang/webdev/pull/2070) ## 19.0.0 diff --git a/dwds/lib/src/debugging/classes.dart b/dwds/lib/src/debugging/classes.dart index 256240dcb..87b8e6d94 100644 --- a/dwds/lib/src/debugging/classes.dart +++ b/dwds/lib/src/debugging/classes.dart @@ -199,8 +199,7 @@ class ClassHelper extends Domain { fieldDescriptors.forEach((name, descriptor) { final classMetaData = ClassMetaData( jsName: descriptor['classRefName'], - //libraryId: descriptor['classRefLibraryId'], - //dartName: descriptor['classRefDartName'], + kind: InstanceKind.kType, classRef: classRefFor( descriptor['classRefLibraryId'], descriptor['classRefDartName'], @@ -213,8 +212,7 @@ class ClassHelper extends Domain { declaredType: InstanceRef( identityHashCode: createId().hashCode, id: createId(), - kind: InstanceKind.kType, - // TODO(elliette): Is this the same as classRef? + kind: classMetaData.kind, classRef: classMetaData.classRef, ), isConst: descriptor['isConst'] as bool, diff --git a/dwds/lib/src/debugging/instance.dart b/dwds/lib/src/debugging/instance.dart index c6d9b26b9..f6e389e9c 100644 --- a/dwds/lib/src/debugging/instance.dart +++ b/dwds/lib/src/debugging/instance.dart @@ -114,12 +114,6 @@ class InstanceHelper extends Domain { final objectId = remoteObject?.objectId; if (remoteObject == null || objectId == null) return null; - // TODO: This is checking the JS object ID for the dart pattern we use for - // VM objects, which seems wrong (and, we catch 'string' types above). - if (isStringId(objectId)) { - return _stringInstanceFor(remoteObject, offset, count); - } - final metaData = await ClassMetaData.metaDataFor( remoteObject, inspector, @@ -180,68 +174,6 @@ class InstanceHelper extends Domain { length: metaData.length, ); } - /* - if (metaData.isFunction) { - return _closureInstanceFor(remoteObject); - } - - if (metaData.isSystemList) { - return await _listInstanceFor( - classRef, - remoteObject, - offset: offset, - count: count, - length: metaData.length, - ); - } else if (metaData.isSystemMap) { - return await _mapInstanceFor( - classRef, - remoteObject, - offset: offset, - count: count, - length: metaData.length, - ); - } else if (metaData.isRecord) { - return await _recordInstanceFor( - classRef, - remoteObject, - offset: offset, - count: count, - length: metaData.length, - ); - } else if (metaData.isRecordType) { - return await _recordTypeInstanceFor( - classRef, - remoteObject, - offset: offset, - count: count, - length: metaData.length, - ); - } else if (metaData.isSet) { - return await _setInstanceFor( - classRef, - remoteObject, - offset: offset, - count: count, - length: metaData.length, - ); - } else if (metaData.isNativeError) { - return await _plainInstanceFor( - classRefForNativeJsError, - remoteObject, - offset: offset, - count: count, - length: metaData.length, - ); - } else { - return await _plainInstanceFor( - classRef, - remoteObject, - offset: offset, - count: count, - length: metaData.length, - ); - }*/ } /// If [remoteObject] represents a primitive, return an [Instance] for it, diff --git a/dwds/lib/src/debugging/libraries.dart b/dwds/lib/src/debugging/libraries.dart index d152ced30..68b65e57f 100644 --- a/dwds/lib/src/debugging/libraries.dart +++ b/dwds/lib/src/debugging/libraries.dart @@ -121,6 +121,7 @@ class LibraryHelper extends Domain { for (final classDescriptor in classDescriptors) { final classMetaData = ClassMetaData( jsName: classDescriptor['name'], + kind: InstanceKind.kType, classRef: classRefFor( libraryRef.id, classDescriptor['dartName'], diff --git a/dwds/lib/src/debugging/metadata/class.dart b/dwds/lib/src/debugging/metadata/class.dart index eca649d3a..06b90c78e 100644 --- a/dwds/lib/src/debugging/metadata/class.dart +++ b/dwds/lib/src/debugging/metadata/class.dart @@ -146,44 +146,17 @@ class ClassMetaData { factory ClassMetaData({ Object? jsName, - //Object? libraryId, - //Object? dartName, Object? length, - String kind = InstanceKind.kPlainInstance, + required String kind, required ClassRef classRef, - //bool isFunction = false, - //bool isRecord = false, - //bool isRecordType = false, - //bool isNativeError = false, }) { final id = classMetaDataIdFor(classRef.library!.id!, jsName as String?); - // final jName = jsName as String?; - // final dName = dartName as String?; - //final library = libraryId as String? ?? _dartCoreLibrary; - //final id = '$library:$jName'; - - // var classRef = classRefFor(library, dName); - // if (kind == ClassMetaDataKind.record) { - // classRef = classRefForRecord; - // } - // if (kind == ClassMetaDataKind.recordType) { - // classRef = classRefForRecordType; - // } - // if (kind == ClassMetaDataKind.nativeError) { - // classRef = classRefForNativeJsError; - // } - return ClassMetaData._( id, classRef, jsName, - //dartName as String?, int.tryParse('$length'), kind, - // isFunction, - // isRecord, - // isRecordType, - // isNativeError, ); } @@ -191,13 +164,8 @@ class ClassMetaData { this.id, this.classRef, this.jsName, - //this.dartName, this.length, this.kind, - // this.isFunction, - // this.isRecord, - // this.isRecordType, - // this.isNativeError, ); /// Returns the [ClassMetaData] for the Chrome [remoteObject]. @@ -254,22 +222,12 @@ class ClassMetaData { returnByValue: true, ); final metadata = result.value as Map; - final jsName = metadata['name']; - - final kind = _computeInstanceKind(metadata); - final classRef = _computeClassRef(metadata); return ClassMetaData( - jsName: jsName, - //libraryId: library, - //dartName: dartName, - kind: kind, - classRef: classRef, - // isFunction: metadata['isFunction'], - // isRecord: metadata['isRecord'], - // isRecordType: metadata['isRecordType'], - // isNativeError: metadata['isNativeError'], + jsName: metadata['name'], length: metadata['length'], + kind: _getInstanceKind(metadata), + classRef: _getClassRef(metadata), ); } on ChromeDebugException catch (e, s) { _logger.fine( @@ -280,37 +238,10 @@ class ClassMetaData { return null; } } - - /// TODO(annagrin): convert fields and getters below to kinds. - - /// True if this class refers to system maps, which are treated specially. - /// - /// Classes that implement Map or inherit from MapBase are still treated as - /// plain objects. - // TODO(alanknight): It may be that IdentityMap should not be treated as a - // system map. - // bool get isSystemMap => jsName == 'LinkedMap' || jsName == 'IdentityMap'; - - // /// True if this class refers to system Lists, which are treated specially. - // bool get isSystemList => jsName == 'JSArray'; - - // bool get isSet => jsName == '_HashSet'; - - // /// True if this class refers to a function type. - // bool isFunction; - - // /// True if this class refers to a Record type. - // bool isRecord; - - // /// True if this class refers to a RecordType type. - // bool isRecordType; - - // /// True is this class refers to a native JS type. - // /// i.e. inherits from NativeError. - // bool isNativeError; } -String _computeInstanceKind(Map metadata) { +/// Find instance kind of an object from [metadata]. +String _getInstanceKind(Map metadata) { final jsName = metadata['name']; final isFunction = metadata['isFunction']; final isRecord = metadata['isRecord']; @@ -337,7 +268,8 @@ String _computeInstanceKind(Map metadata) { return InstanceKind.kPlainInstance; } -ClassRef _computeClassRef(Map metadata) { +/// Find class ref of an object from [metadata]. +ClassRef _getClassRef(Map metadata) { final dartName = metadata['dartName']; final library = metadata['libraryId']; final isRecord = metadata['isRecord']; diff --git a/dwds/test/metadata/class_test.dart b/dwds/test/metadata/class_test.dart index d0c765ebe..dce372729 100644 --- a/dwds/test/metadata/class_test.dart +++ b/dwds/test/metadata/class_test.dart @@ -6,19 +6,36 @@ import 'package:dwds/src/debugging/metadata/class.dart'; import 'package:test/test.dart'; +import 'package:vm_service/vm_service.dart'; void main() { test('Gracefully handles invalid length objects', () async { - var metadata = ClassMetaData(length: null, classRef: classRefForUnknown); + var metadata = ClassMetaData( + length: null, + kind: InstanceKind.kPlainInstance, + classRef: classRefForUnknown, + ); expect(metadata.length, isNull); - metadata = ClassMetaData(length: {}, classRef: classRefForUnknown); + metadata = ClassMetaData( + length: {}, + kind: InstanceKind.kPlainInstance, + classRef: classRefForUnknown, + ); expect(metadata.length, isNull); - metadata = ClassMetaData(length: '{}', classRef: classRefForUnknown); + metadata = ClassMetaData( + length: '{}', + kind: InstanceKind.kPlainInstance, + classRef: classRefForUnknown, + ); expect(metadata.length, isNull); - metadata = ClassMetaData(length: 0, classRef: classRefForUnknown); + metadata = ClassMetaData( + length: 0, + kind: InstanceKind.kPlainInstance, + classRef: classRefForUnknown, + ); expect(metadata.length, equals(0)); }); } From 0fedc4bf196fc44b27f988fc8f29047acf30e3f3 Mon Sep 17 00:00:00 2001 From: Anna Gringauze Date: Tue, 25 Apr 2023 15:25:19 -0700 Subject: [PATCH 03/13] update changelog ref --- dwds/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index dca5e719e..4cc1af707 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -3,7 +3,7 @@ - Do not show async frame errors on evaluation. - [#2073](https://github.com/dart-lang/webdev/pull/2073) - Refactor code for presenting record instances. - [#2074](https://github.com/dart-lang/webdev/pull/2074) - Display record types concisely. - [#2070](https://github.com/dart-lang/webdev/pull/2070) -- Simplify class metadata. - [#2070](https://github.com/dart-lang/webdev/pull/2070) +- Simplify class metadata. - [#2102](https://github.com/dart-lang/webdev/pull/2102) ## 19.0.0 From e5f2938615a35d16db47e4af42d420c47084755e Mon Sep 17 00:00:00 2001 From: Anna Gringauze Date: Tue, 25 Apr 2023 15:28:56 -0700 Subject: [PATCH 04/13] Cleanup --- dwds/lib/src/debugging/instance.dart | 63 ++-------------------------- 1 file changed, 4 insertions(+), 59 deletions(-) diff --git a/dwds/lib/src/debugging/instance.dart b/dwds/lib/src/debugging/instance.dart index f6e389e9c..92612a1a9 100644 --- a/dwds/lib/src/debugging/instance.dart +++ b/dwds/lib/src/debugging/instance.dart @@ -860,70 +860,15 @@ class InstanceHelper extends Domain { identityHashCode: remoteObject.objectId.hashCode, classRef: metaData.classRef, )..length = metaData.length; - - // if (metaData.isSystemList) { - // return InstanceRef( - // kind: InstanceKind.kList, - // id: objectId, - // identityHashCode: remoteObject.objectId.hashCode, - // classRef: metaData.classRef, - // )..length = metaData.length; - // } - // if (metaData.isSystemMap) { - // return InstanceRef( - // kind: InstanceKind.kMap, - // id: objectId, - // identityHashCode: remoteObject.objectId.hashCode, - // classRef: metaData.classRef, - // )..length = metaData.length; - // } - // if (metaData.isRecord) { - // return InstanceRef( - // kind: InstanceKind.kRecord, - // id: objectId, - // identityHashCode: remoteObject.objectId.hashCode, - // classRef: metaData.classRef, - // )..length = metaData.length; - // } - // if (metaData.isRecordType) { - // return InstanceRef( - // kind: InstanceKind.kRecordType, - // id: objectId, - // identityHashCode: remoteObject.objectId.hashCode, - // classRef: metaData.classRef, - // )..length = metaData.length; - // } - // if (metaData.isSet) { - // return InstanceRef( - // kind: InstanceKind.kSet, - // id: objectId, - // identityHashCode: remoteObject.objectId.hashCode, - // classRef: metaData.classRef, - // )..length = metaData.length; - // } - // if (metaData.isNativeError) { - // return InstanceRef( - // kind: InstanceKind.kPlainInstance, - // id: objectId, - // identityHashCode: remoteObject.objectId.hashCode, - // classRef: classRefForNativeJsError, - // )..length = metaData.length; - // } - // return InstanceRef( - // kind: InstanceKind.kPlainInstance, - // id: objectId, - // identityHashCode: remoteObject.objectId.hashCode, - // classRef: metaData.classRef, - // ); case 'function': - final functionMetaData = await FunctionMetaData.metaDataFor( - inspector.remoteDebugger, - remoteObject, - ); final objectId = remoteObject.objectId; if (objectId == null) { return _primitiveInstanceRef(InstanceKind.kNull, remoteObject); } + final functionMetaData = await FunctionMetaData.metaDataFor( + inspector.remoteDebugger, + remoteObject, + ); return InstanceRef( kind: InstanceKind.kClosure, id: objectId, From ae937ac087de673fd13be48819585b29f798a077 Mon Sep 17 00:00:00 2001 From: Anna Gringauze Date: Fri, 28 Apr 2023 17:23:19 -0700 Subject: [PATCH 05/13] Hide internal details in type presentation --- dwds/lib/src/debugging/classes.dart | 2 +- dwds/lib/src/debugging/dart_scope.dart | 6 +- dwds/lib/src/debugging/debugger.dart | 149 +------- dwds/lib/src/debugging/inspector.dart | 171 +++++++-- dwds/lib/src/debugging/instance.dart | 192 +++++++++-- dwds/lib/src/debugging/libraries.dart | 2 +- dwds/lib/src/debugging/metadata/class.dart | 262 +++++++------- .../batched_expression_evaluator.dart | 10 +- .../src/services/chrome_proxy_service.dart | 1 - .../src/services/expression_evaluator.dart | 2 +- dwds/lib/src/utilities/domain.dart | 17 + dwds/test/fixtures/fakes.dart | 9 +- dwds/test/inspector_test.dart | 5 +- .../instances/instance_inspection_common.dart | 48 +-- dwds/test/instances/instance_test.dart | 9 +- .../record_type_inspection_test.dart | 102 +++--- dwds/test/instances/type_inspection_test.dart | 325 ++++++++++++++++++ dwds/test/metadata/class_test.dart | 31 +- 18 files changed, 885 insertions(+), 458 deletions(-) create mode 100644 dwds/test/instances/type_inspection_test.dart diff --git a/dwds/lib/src/debugging/classes.dart b/dwds/lib/src/debugging/classes.dart index 87b8e6d94..bc425141a 100644 --- a/dwds/lib/src/debugging/classes.dart +++ b/dwds/lib/src/debugging/classes.dart @@ -199,7 +199,7 @@ class ClassHelper extends Domain { fieldDescriptors.forEach((name, descriptor) { final classMetaData = ClassMetaData( jsName: descriptor['classRefName'], - kind: InstanceKind.kType, + runtimeKind: RuntimeObjectKind.type, classRef: classRefFor( descriptor['classRefLibraryId'], descriptor['classRefDartName'], diff --git a/dwds/lib/src/debugging/dart_scope.dart b/dwds/lib/src/debugging/dart_scope.dart index efd53aba6..341af8904 100644 --- a/dwds/lib/src/debugging/dart_scope.dart +++ b/dwds/lib/src/debugging/dart_scope.dart @@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'package:dwds/src/debugging/debugger.dart'; +import 'package:dwds/src/utilities/domain.dart'; import 'package:dwds/src/utilities/objects.dart'; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; @@ -25,7 +25,7 @@ final previousDdcTemporaryVariableRegExp = /// /// See chromedevtools.github.io/devtools-protocol/tot/Debugger#type-CallFrame. Future> visibleProperties({ - required Debugger debugger, + required AppInspectorInterface inspector, required WipCallFrame frame, }) async { final allProperties = []; @@ -48,7 +48,7 @@ Future> visibleProperties({ for (var scope in filterScopes(frame).reversed) { final objectId = scope.object.objectId; if (objectId != null) { - final properties = await debugger.getProperties(objectId); + final properties = await inspector.getProperties(objectId); allProperties.addAll(properties); } } diff --git a/dwds/lib/src/debugging/debugger.dart b/dwds/lib/src/debugging/debugger.dart index fba98ee1e..4812ab85b 100644 --- a/dwds/lib/src/debugging/debugger.dart +++ b/dwds/lib/src/debugging/debugger.dart @@ -3,17 +3,14 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; -import 'dart:math' as math; import 'package:dwds/src/debugging/dart_scope.dart'; import 'package:dwds/src/debugging/frame_computer.dart'; import 'package:dwds/src/debugging/location.dart'; -import 'package:dwds/src/debugging/metadata/class.dart'; import 'package:dwds/src/debugging/remote_debugger.dart'; import 'package:dwds/src/debugging/skip_list.dart'; import 'package:dwds/src/loaders/strategy.dart'; import 'package:dwds/src/services/chrome_debug_exception.dart'; -import 'package:dwds/src/utilities/conversions.dart'; import 'package:dwds/src/utilities/dart_uri.dart'; import 'package:dwds/src/utilities/domain.dart'; import 'package:dwds/src/utilities/objects.dart' show Property; @@ -49,6 +46,8 @@ class Debugger extends Domain { final SkipLists _skipLists; final String _root; + //final classMetaDataHelper = ClassMetaDataHelper(); + Debugger._( this._remoteDebugger, this._streamNotify, @@ -394,7 +393,8 @@ class Debugger extends Domain { /// The variables visible in a frame in Dart protocol [BoundVariable] form. Future> variablesFor(WipCallFrame frame) async { // TODO(alanknight): Can these be moved to dart_scope.dart? - final properties = await visibleProperties(debugger: this, frame: frame); + final properties = + await visibleProperties(inspector: inspector, frame: frame); final boundVariables = await Future.wait( properties.map(_boundVariable), ); @@ -402,7 +402,7 @@ class Debugger extends Domain { // Filter out variables that do not come from dart code, such as native // JavaScript objects return boundVariables - .where((bv) => isDisplayableObject(bv?.value)) + .where((bv) => inspector.isDisplayableObject(bv?.value)) .toList() .cast(); } @@ -432,84 +432,6 @@ class Debugger extends Domain { return null; } - static bool _isEmptyRange({ - required int length, - int? offset, - int? count, - }) { - if (count == 0) return true; - if (offset == null) return false; - return offset >= length; - } - - static bool _isSubRange({ - int? offset, - int? count, - }) { - if (offset == 0 && count == null) return false; - return offset != null || count != null; - } - - /// Compute the last possible element index in the range of [offset]..end - /// that includes [count] elements, if available. - static int? _calculateRangeEnd({ - int? count, - required int offset, - required int length, - }) => - count == null ? null : math.min(offset + count, length); - - /// Calculate the number of available elements in the range. - static int _calculateRangeCount({ - int? count, - required int offset, - required int length, - }) => - count == null ? length - offset : math.min(count, length - offset); - - /// Find a sub-range of the entries for a Map/List when offset and/or count - /// have been specified on a getObject request. - /// - /// If the object referenced by [id] is not a system List or Map then this - /// will just return a RemoteObject for it and ignore [offset], [count] and - /// [length]. If it is, then [length] should be the number of entries in the - /// List/Map and [offset] and [count] should indicate the desired range. - Future _subRange( - String id, { - required int offset, - int? count, - required int length, - }) async { - // TODO(#809): Sometimes we already know the type of the object, and - // we could take advantage of that to short-circuit. - final receiver = remoteObjectFor(id); - final end = - _calculateRangeEnd(count: count, offset: offset, length: length); - final rangeCount = - _calculateRangeCount(count: count, offset: offset, length: length); - final args = - [offset, rangeCount, end].map(dartIdFor).map(remoteObjectFor).toList(); - // If this is a List, just call sublist. If it's a Map, get the entries, but - // avoid doing a toList on a large map using skip/take to get the section we - // want. To make those alternatives easier in JS, pass both count and end. - final expression = ''' - function (offset, count, end) { - const sdk = ${globalLoadStrategy.loadModuleSnippet}("dart_sdk"); - if (sdk.core.Map.is(this)) { - const entries = sdk.dart.dload(this, "entries"); - const skipped = sdk.dart.dsend(entries, "skip", [offset]) - const taken = sdk.dart.dsend(skipped, "take", [count]); - return sdk.dart.dsend(taken, "toList", []); - } else if (sdk.core.List.is(this)) { - return sdk.dart.dsendRepl(this, "sublist", [offset, end]); - } else { - return this; - } - } - '''; - return await inspector.jsCallFunctionOn(receiver, expression, args); - } - // TODO(elliette): https://github.com/dart-lang/webdev/issues/1501 Re-enable // after checking with Chrome team if there is a way to check if the Chrome // DevTools is showing an overlay. Both cannot be shown at the same time: @@ -533,48 +455,6 @@ class Debugger extends Domain { // _pausedOverlayVisible = false; // } - /// Calls the Chrome Runtime.getProperties API for the object with [objectId]. - /// - /// Note that the property names are JS names, e.g. - /// Symbol(DartClass.actualName) and will need to be converted. For a system - /// List or Map, [offset] and/or [count] can be provided to indicate a desired - /// range of entries. They will be ignored if there is no [length]. - Future> getProperties( - String objectId, { - int? offset, - int? count, - int? length, - }) async { - String rangeId = objectId; - // Ignore offset/count if there is no length: - if (length != null) { - if (_isEmptyRange(offset: offset, count: count, length: length)) { - return []; - } - if (_isSubRange(offset: offset, count: count)) { - final range = await _subRange( - objectId, - offset: offset ?? 0, - count: count, - length: length, - ); - rangeId = range.objectId ?? rangeId; - } - } - final jsProperties = await sendCommandAndValidateResult( - _remoteDebugger, - method: 'Runtime.getProperties', - resultField: 'result', - params: { - 'objectId': rangeId, - 'ownProperties': true, - }, - ); - return jsProperties - .map((each) => Property(each as Map)) - .toList(); - } - /// Returns a Dart [Frame] for a JS [frame]. Future calculateDartFrameFor( WipCallFrame frame, @@ -658,7 +538,7 @@ class Debugger extends Domain { if (map['type'] == 'object') { final obj = RemoteObject(map); exception = await inspector.instanceRefFor(obj); - if (exception != null && isNativeJsError(exception)) { + if (exception != null && inspector.isNativeJsError(exception)) { if (obj.description != null) { // Create a string exception object. final description = @@ -845,23 +725,6 @@ Future sendCommandAndValidateResult( return result; } -/// Returns true for objects we display for the user. -bool isDisplayableObject(Object? object) => - object is Sentinel || - object is InstanceRef && - !isNativeJsObject(object) && - !isNativeJsError(object); - -/// Returns true for non-dart JavaScript objects. -bool isNativeJsObject(InstanceRef instanceRef) { - return isNativeJsObjectRef(instanceRef.classRef); -} - -/// Returns true of JavaScript exceptions. -bool isNativeJsError(InstanceRef instanceRef) { - return instanceRef.classRef == classRefForNativeJsError; -} - /// Returns the Dart line number for the provided breakpoint. int _lineNumberFor(Breakpoint breakpoint) => int.parse(breakpoint.id!.split('#').last.split(':').first); diff --git a/dwds/lib/src/debugging/inspector.dart b/dwds/lib/src/debugging/inspector.dart index ba9178dce..40f95a9a2 100644 --- a/dwds/lib/src/debugging/inspector.dart +++ b/dwds/lib/src/debugging/inspector.dart @@ -2,6 +2,8 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:math' as math; + import 'package:async/async.dart'; import 'package:collection/collection.dart'; import 'package:dwds/src/connections/app_connection.dart'; @@ -17,6 +19,7 @@ import 'package:dwds/src/readers/asset_reader.dart'; import 'package:dwds/src/utilities/conversions.dart'; import 'package:dwds/src/utilities/dart_uri.dart'; import 'package:dwds/src/utilities/domain.dart'; +import 'package:dwds/src/utilities/objects.dart'; import 'package:dwds/src/utilities/server.dart'; import 'package:dwds/src/utilities/shared.dart'; import 'package:logging/logging.dart'; @@ -96,17 +99,13 @@ class AppInspector implements AppInspectorInterface { this._locations, this._root, this._executionContext, - ) : _isolateRef = _toIsolateRef(_isolate); - - Future initialize( - LibraryHelper libraryHelper, - ClassHelper classHelper, - InstanceHelper instanceHelper, - ) async { - _libraryHelper = libraryHelper; - _classHelper = classHelper; - _instanceHelper = instanceHelper; + ) : _isolateRef = _toIsolateRef(_isolate) { + _libraryHelper = LibraryHelper(this); + _classHelper = ClassHelper(this); + _instanceHelper = InstanceHelper(this); + } + Future initialize() async { final libraries = await _libraryHelper.libraryRefs; isolate.rootLib = await _libraryHelper.rootLib; isolate.libraries?.addAll(libraries); @@ -176,16 +175,7 @@ class AppInspector implements AppInspectorInterface { ); debugger.updateInspector(inspector); - - final libraryHelper = LibraryHelper(inspector); - final classHelper = ClassHelper(inspector); - final instanceHelper = InstanceHelper(inspector, debugger); - - await inspector.initialize( - libraryHelper, - classHelper, - instanceHelper, - ); + await inspector.initialize(); return inspector; } @@ -568,6 +558,147 @@ class AppInspector implements AppInspectorInterface { return ScriptList(scripts: await scriptRefs); } + /// Calls the Chrome Runtime.getProperties API for the object with [objectId]. + /// + /// Note that the property names are JS names, e.g. + /// Symbol(DartClass.actualName) and will need to be converted. For a system + /// List or Map, [offset] and/or [count] can be provided to indicate a desired + /// range of entries. They will be ignored if there is no [length]. + @override + Future> getProperties( + String objectId, { + int? offset, + int? count, + int? length, + }) async { + String rangeId = objectId; + // Ignore offset/count if there is no length: + if (length != null) { + if (_isEmptyRange(offset: offset, count: count, length: length)) { + return []; + } + if (_isSubRange(offset: offset, count: count)) { + final range = await _subRange( + objectId, + offset: offset ?? 0, + count: count, + length: length, + ); + rangeId = range.objectId ?? rangeId; + } + } + final jsProperties = await sendCommandAndValidateResult( + _remoteDebugger, + method: 'Runtime.getProperties', + resultField: 'result', + params: { + 'objectId': rangeId, + 'ownProperties': true, + }, + ); + return jsProperties + .map((each) => Property(each as Map)) + .toList(); + } + + /// Compute the last possible element index in the range of [offset]..end + /// that includes [count] elements, if available. + static int? _calculateRangeEnd({ + int? count, + required int offset, + required int length, + }) => + count == null ? null : math.min(offset + count, length); + + /// Calculate the number of available elements in the range. + static int _calculateRangeCount({ + int? count, + required int offset, + required int length, + }) => + count == null ? length - offset : math.min(count, length - offset); + + /// Find a sub-range of the entries for a Map/List when offset and/or count + /// have been specified on a getObject request. + /// + /// If the object referenced by [id] is not a system List or Map then this + /// will just return a RemoteObject for it and ignore [offset], [count] and + /// [length]. If it is, then [length] should be the number of entries in the + /// List/Map and [offset] and [count] should indicate the desired range. + Future _subRange( + String id, { + required int offset, + int? count, + required int length, + }) async { + // TODO(#809): Sometimes we already know the type of the object, and + // we could take advantage of that to short-circuit. + final receiver = remoteObjectFor(id); + final end = + _calculateRangeEnd(count: count, offset: offset, length: length); + final rangeCount = + _calculateRangeCount(count: count, offset: offset, length: length); + final args = + [offset, rangeCount, end].map(dartIdFor).map(remoteObjectFor).toList(); + // If this is a List, just call sublist. If it's a Map, get the entries, but + // avoid doing a toList on a large map using skip/take to get the section we + // want. To make those alternatives easier in JS, pass both count and end. + final expression = ''' + function (offset, count, end) { + const sdk = ${globalLoadStrategy.loadModuleSnippet}("dart_sdk"); + if (sdk.core.Map.is(this)) { + const entries = sdk.dart.dload(this, "entries"); + const skipped = sdk.dart.dsend(entries, "skip", [offset]) + const taken = sdk.dart.dsend(skipped, "take", [count]); + return sdk.dart.dsend(taken, "toList", []); + } else if (sdk.core.List.is(this)) { + return sdk.dart.dsendRepl(this, "sublist", [offset, end]); + } else { + return this; + } + } + '''; + return await jsCallFunctionOn(receiver, expression, args); + } + + static bool _isEmptyRange({ + required int length, + int? offset, + int? count, + }) { + if (count == 0) return true; + if (offset == null) return false; + return offset >= length; + } + + static bool _isSubRange({ + int? offset, + int? count, + }) { + if (offset == 0 && count == null) return false; + return offset != null || count != null; + } + + /// Returns true for objects we display for the user. + @override + bool isDisplayableObject(Object? object) => + object is Sentinel || + object is InstanceRef && + !isNativeJsObject(object) && + !isNativeJsError(object); + + /// Returns true for non-dart JavaScript objects. + bool isNativeJsObject(InstanceRef instanceRef) { + return _instanceHelper.metadataHelper + .isNativeJsObject(instanceRef.classRef); + } + + /// Returns true of JavaScript exceptions. + @override + bool isNativeJsError(InstanceRef instanceRef) { + return _instanceHelper.metadataHelper.isNativeJsError(instanceRef.classRef); + } + /// Request and cache s for all the scripts in the application. /// /// This populates [_scriptRefsById], [_scriptIdToLibraryId], diff --git a/dwds/lib/src/debugging/instance.dart b/dwds/lib/src/debugging/instance.dart index 92612a1a9..93d6db487 100644 --- a/dwds/lib/src/debugging/instance.dart +++ b/dwds/lib/src/debugging/instance.dart @@ -4,7 +4,7 @@ import 'dart:math'; -import 'package:dwds/src/debugging/debugger.dart'; +import 'package:dwds/src/debugging/inspector.dart'; import 'package:dwds/src/debugging/metadata/class.dart'; import 'package:dwds/src/debugging/metadata/function.dart'; import 'package:dwds/src/loaders/strategy.dart'; @@ -19,12 +19,13 @@ import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; /// Contains a set of methods for getting [Instance]s and [InstanceRef]s. class InstanceHelper extends Domain { final _logger = Logger('InstanceHelper'); + final ClassMetaDataHelper metadataHelper; - InstanceHelper(AppInspectorInterface appInspector, this.debugger) { + InstanceHelper(AppInspector appInspector) + : metadataHelper = ClassMetaDataHelper(appInspector) { inspector = appInspector; } - final Debugger debugger; static final InstanceRef kNullInstanceRef = _primitiveInstanceRef(InstanceKind.kNull, null); @@ -114,18 +115,15 @@ class InstanceHelper extends Domain { final objectId = remoteObject?.objectId; if (remoteObject == null || objectId == null) return null; - final metaData = await ClassMetaData.metaDataFor( - remoteObject, - inspector, - ); + final metaData = await metadataHelper.metaDataFor(remoteObject); final classRef = metaData?.classRef; if (metaData == null || classRef == null) return null; - switch (metaData.kind) { - case InstanceKind.kClosure: + switch (metaData.runtimeKind) { + case RuntimeObjectKind.function: return _closureInstanceFor(remoteObject); - case InstanceKind.kList: + case RuntimeObjectKind.list: return await _listInstanceFor( classRef, remoteObject, @@ -133,7 +131,7 @@ class InstanceHelper extends Domain { count: count, length: metaData.length, ); - case InstanceKind.kSet: + case RuntimeObjectKind.set: return await _setInstanceFor( classRef, remoteObject, @@ -141,7 +139,7 @@ class InstanceHelper extends Domain { count: count, length: metaData.length, ); - case InstanceKind.kMap: + case RuntimeObjectKind.map: return await _mapInstanceFor( classRef, remoteObject, @@ -149,7 +147,7 @@ class InstanceHelper extends Domain { count: count, length: metaData.length, ); - case InstanceKind.kRecord: + case RuntimeObjectKind.record: return await _recordInstanceFor( classRef, remoteObject, @@ -157,7 +155,7 @@ class InstanceHelper extends Domain { count: count, length: metaData.length, ); - case InstanceKind.kRecordType: + case RuntimeObjectKind.recordType: return await _recordTypeInstanceFor( classRef, remoteObject, @@ -165,6 +163,24 @@ class InstanceHelper extends Domain { count: count, length: metaData.length, ); + case RuntimeObjectKind.type: + return await _plainTypeInstanceFor( + classRef, + remoteObject, + offset: offset, + count: count, + length: metaData.length, + ); + case RuntimeObjectKind.wrappedType: + return await _wrappedTypeInstanceFor( + classRef, + remoteObject, + offset: offset, + count: count, + ); + case RuntimeObjectKind.object: + case RuntimeObjectKind.nativeError: + case RuntimeObjectKind.nativeObject: default: return await _plainInstanceFor( classRef, @@ -234,7 +250,7 @@ class InstanceHelper extends Domain { final objectId = remoteObject.objectId; if (objectId == null) return null; - final properties = await debugger.getProperties( + final properties = await inspector.getProperties( objectId, offset: offset, count: count, @@ -245,7 +261,7 @@ class InstanceHelper extends Domain { dartProperties.map>((p) => _fieldFor(p, classRef)), ); boundFields = boundFields - .where((bv) => isDisplayableObject(bv.value)) + .where((bv) => inspector.isDisplayableObject(bv.value)) .toList() ..sort(_compareBoundFields); final result = Instance( @@ -410,7 +426,7 @@ class InstanceHelper extends Domain { int? count, int? length, }) async { - final properties = await debugger.getProperties( + final properties = await inspector.getProperties( list.objectId!, offset: offset, count: count, @@ -731,6 +747,113 @@ class InstanceHelper extends Domain { return setInstance; } + /// Create Type instance with class [classRef] from [remoteObject]. + /// + /// Finds the internal type and returns its object representation. + /// + /// Returns an instance containing [count] fields, if available, + /// starting from the [offset]. + /// + /// If [offset] is `null`, assumes 0 offset. + /// If [count] is `null`, return all fields starting from the offset. + Future _wrappedTypeInstanceFor( + ClassRef classRef, + RemoteObject remoteObject, { + int? offset, + int? count, + }) async { + final objectId = remoteObject.objectId; + if (objectId == null) return null; + + final unwrappedType = await _internalType(remoteObject); + return instanceFor(unwrappedType, offset: offset, count: count); + } + + /// Get inner type from the DDC [type] wrapper instance. + Future _internalType(RemoteObject type) { + final expression = ''' + function() { + var sdkUtils = ${globalLoadStrategy.loadModuleSnippet}('dart_sdk').dart; + return sdkUtils.dloadRepl(this, "_type"); + } + '''; + + return inspector.jsCallFunctionOn(type, expression, []); + } + + /// Create Type instance with class [classRef] from [remoteObject]. + /// + /// Collect information from the internal [remoteObject] and present + /// it as an instance of [Type] class. + /// + /// Returns an instance containing `hashCode` and `runtimeType` fields. + /// [length] is the expected length of the whole object, read from + /// the [ClassMetaData]. + Future _plainTypeInstanceFor( + ClassRef classRef, + RemoteObject remoteObject, { + int? offset, + int? count, + int? length, + }) async { + final objectId = remoteObject.objectId; + if (objectId == null) return null; + + final fields = await _typeFields(classRef, remoteObject); + return Instance( + identityHashCode: objectId.hashCode, + kind: InstanceKind.kPlainInstance, + id: objectId, + classRef: classRef, + ) + ..length = length + ..offset = offset + ..count = count + ..fields = fields; + } + + /// The field types for a Dart RecordType. + /// + /// Returns a range of [count] field types, if available, starting from + /// the [offset]. + /// + /// If [offset] is `null`, assumes 0 offset. + /// If [count] is `null`, return all field types starting from the offset. + Future> _typeFields( + ClassRef classRef, + RemoteObject type, + ) async { + // Present the type as an instance of `core.Type` class and + // hide the internal implementation. + final expression = ''' + function() { + var sdkUtils = ${globalLoadStrategy.loadModuleSnippet}('dart_sdk').dart; + var externalType = sdkUtils.wrapType(this); + var hashCode = sdkUtils.dloadRepl(externalType, "hashCode"); + var runtimeType = sdkUtils.dloadRepl(externalType, "runtimeType"); + + return { + hashCode: hashCode, + runtimeType: runtimeType + }; + } + '''; + + final result = await inspector.jsCallFunctionOn(type, expression, []); + final hashCodeObject = await inspector.loadField(result, 'hashCode'); + final runtimeTypeObject = await inspector.loadField(result, 'runtimeType'); + + final properties = [ + Property({'name': 'hashCode', 'value': hashCodeObject}), + Property({'name': 'runtimeType', 'value': runtimeTypeObject}), + ]; + + final boundFields = await Future.wait( + properties.map>((p) => _fieldFor(p, classRef)), + ); + return boundFields; + } + /// Return the available count of elements in the requested range. /// Return `null` if the range includes the whole object. /// [count] is the range length requested by the `getObject` call. @@ -817,6 +940,29 @@ class InstanceHelper extends Domain { return 'object'; } + /// Create an [InstanceRef] for the given Chrome [remoteObject] + /// for a wrapped type. + /// + /// Make sure the instance kind and class ref on the ref matches + /// the one on the instance (the internal type instance kind). + Future _wrappedTypeInstanceRef( + RemoteObject remoteObject, + ) async { + final objectId = remoteObject.objectId; + if (objectId == null) return null; + + final unwrappedObject = await _internalType(remoteObject); + final metaData = await metadataHelper.metaDataFor(unwrappedObject); + if (metaData == null) return null; + + return InstanceRef( + kind: metaData.kind, + id: objectId, + identityHashCode: objectId.hashCode, + classRef: metaData.classRef, + )..length = metaData.length; + } + /// Create an [InstanceRef] for the given Chrome [remoteObject]. Future _instanceRefForRemote(RemoteObject? remoteObject) async { // If we have a null result, treat it as a reference to null. @@ -849,15 +995,15 @@ class InstanceHelper extends Domain { if (objectId == null) { return _primitiveInstanceRef(InstanceKind.kNull, remoteObject); } - final metaData = await ClassMetaData.metaDataFor( - remoteObject, - inspector, - ); + final metaData = await metadataHelper.metaDataFor(remoteObject); if (metaData == null) return null; + if (metaData.runtimeKind == RuntimeObjectKind.wrappedType) { + return _wrappedTypeInstanceRef(remoteObject); + } return InstanceRef( kind: metaData.kind, id: objectId, - identityHashCode: remoteObject.objectId.hashCode, + identityHashCode: objectId.hashCode, classRef: metaData.classRef, )..length = metaData.length; case 'function': @@ -872,7 +1018,7 @@ class InstanceHelper extends Domain { return InstanceRef( kind: InstanceKind.kClosure, id: objectId, - identityHashCode: remoteObject.objectId.hashCode, + identityHashCode: objectId.hashCode, classRef: classRefForClosure, ) // TODO(grouma) - fill this in properly. diff --git a/dwds/lib/src/debugging/libraries.dart b/dwds/lib/src/debugging/libraries.dart index 68b65e57f..b50eef16d 100644 --- a/dwds/lib/src/debugging/libraries.dart +++ b/dwds/lib/src/debugging/libraries.dart @@ -121,7 +121,7 @@ class LibraryHelper extends Domain { for (final classDescriptor in classDescriptors) { final classMetaData = ClassMetaData( jsName: classDescriptor['name'], - kind: InstanceKind.kType, + runtimeKind: RuntimeObjectKind.type, classRef: classRefFor( libraryRef.id, classDescriptor['dartName'], diff --git a/dwds/lib/src/debugging/metadata/class.dart b/dwds/lib/src/debugging/metadata/class.dart index 06b90c78e..70e83d99e 100644 --- a/dwds/lib/src/debugging/metadata/class.dart +++ b/dwds/lib/src/debugging/metadata/class.dart @@ -11,7 +11,6 @@ import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; const _dartCoreLibrary = 'dart:core'; const _dartRuntimeLibrary = 'dart:_runtime'; -const _dartInterceptorsLibrary = 'dart:_interceptors'; /// A hard-coded ClassRef for the Closure class. final classRefForClosure = classRefFor(_dartCoreLibrary, InstanceKind.kClosure); @@ -26,36 +25,12 @@ final classRefForRecord = classRefFor(_dartCoreLibrary, InstanceKind.kRecord); final classRefForRecordType = classRefFor(_dartRuntimeLibrary, InstanceKind.kRecordType); +/// A hard-coded ClassRef for the Type class. +final classRefForType = classRefFor(_dartCoreLibrary, 'Type'); + /// A hard-coded ClassRef for a (non-existent) class called Unknown. final classRefForUnknown = classRefFor(_dartCoreLibrary, 'Unknown'); -/// A hard-coded ClassRef for a JS exception. -/// -/// Exceptions are instances of NativeError and its subtypes. -/// We detect their common base type in class metadata and replace their -/// classRef by hard-coded reference in instances and instance refs. -/// -/// TODO(annagrin): this breaks on name changes for JS types. -/// https://github.com/dart-lang/sdk/issues/51583 -final classRefForNativeJsError = - classRefFor(_dartInterceptorsLibrary, 'NativeError'); - -String classMetaDataIdFor(String library, String? jsName) => '$library:$jsName'; - -/// Returns true for non-dart JavaScript classes. -/// -/// TODO(annagrin): this breaks on name changes for JS types. -/// https://github.com/dart-lang/sdk/issues/51583 -bool isNativeJsObjectRef(ClassRef? classRef) { - final className = classRef?.name; - final libraryUri = classRef?.library?.uri; - // Non-dart JS objects are all instances of JavaScriptObject - // and its subtypes with names that end with 'JavaScriptObject'. - return className != null && - libraryUri == _dartInterceptorsLibrary && - className.endsWith('JavaScriptObject'); -} - /// A hard-coded LibraryRef for a a dart:core library. final libraryRefForCore = LibraryRef( id: _dartCoreLibrary, @@ -82,45 +57,31 @@ ClassRef classRefFor(Object? libraryId, Object? dartName) { } String classIdFor(String libraryId, String? name) => 'classes|$libraryId|$name'; +String classMetaDataIdFor(String library, String? jsName) => '$library:$jsName'; -enum ClassMetaDataKind { - object, - map, - set, - list, - function, - record, - recordType, - nativeError, -} - -String toInstanceKind(ClassMetaDataKind kind) { - switch (kind) { - case ClassMetaDataKind.function: - return InstanceKind.kClosure; - case ClassMetaDataKind.list: - return InstanceKind.kList; - case ClassMetaDataKind.map: - return InstanceKind.kMap; - case ClassMetaDataKind.set: - return InstanceKind.kSet; - case ClassMetaDataKind.record: - return InstanceKind.kRecord; - case ClassMetaDataKind.recordType: - return InstanceKind.kRecordType; - case ClassMetaDataKind.nativeError: - case ClassMetaDataKind.object: - default: - return InstanceKind.kPlainInstance; - } +/// DDC runtime object kind. +/// +/// Object kinds are determined using DDC runtime API and +/// are used to translate from JavaScript objects to their +/// vm service protocol representation. +class RuntimeObjectKind { + static const String object = 'object'; + static const String set = 'set'; + static const String list = 'list'; + static const String map = 'map'; + static const String wrappedType = 'wrappedType'; + static const String function = 'function'; + static const String record = 'record'; + static const String type = 'type'; + static const String recordType = 'recordType'; + static const String nativeError = 'nativeError'; + static const String nativeObject = 'nativeObject'; } /// Meta data for a remote Dart class in Chrome. class ClassMetaData { - static final _logger = Logger('ClassMetadata'); - - /// Instance kind. - final String kind; + /// Runtime object kind. + final String runtimeKind; /// Class id. /// @@ -144,10 +105,13 @@ class ClassMetaData { /// Class ref for the class metadata. final ClassRef classRef; + /// Instance kind for vm service protocol. + String get kind => _toInstanceKind(runtimeKind); + factory ClassMetaData({ Object? jsName, Object? length, - required String kind, + required String runtimeKind, required ClassRef classRef, }) { final id = classMetaDataIdFor(classRef.library!.id!, jsName as String?); @@ -156,7 +120,7 @@ class ClassMetaData { classRef, jsName, int.tryParse('$length'), - kind, + runtimeKind, ); } @@ -165,17 +129,52 @@ class ClassMetaData { this.classRef, this.jsName, this.length, - this.kind, + this.runtimeKind, ); + static String _toInstanceKind(String runtimeKind) { + switch (runtimeKind) { + case RuntimeObjectKind.function: + return InstanceKind.kClosure; + case RuntimeObjectKind.list: + return InstanceKind.kList; + case RuntimeObjectKind.map: + return InstanceKind.kMap; + case RuntimeObjectKind.set: + return InstanceKind.kSet; + case RuntimeObjectKind.record: + return InstanceKind.kRecord; + case RuntimeObjectKind.recordType: + return InstanceKind.kRecordType; + case RuntimeObjectKind.object: + case RuntimeObjectKind.type: + case RuntimeObjectKind.wrappedType: + case RuntimeObjectKind.nativeError: + case RuntimeObjectKind.nativeObject: + default: + return InstanceKind.kPlainInstance; + } + } +} + +class ClassMetaDataHelper { + static final _logger = Logger('ClassMetadata'); + + final AppInspectorInterface _inspector; + + /// Runtime object kinds for class refs. + final _runtimeObjectKinds = {}; + + ClassMetaDataHelper(this._inspector); + /// Returns the [ClassMetaData] for the Chrome [remoteObject]. /// /// Returns null if the [remoteObject] is not a Dart class. - static Future metaDataFor( - RemoteObject remoteObject, - AppInspectorInterface inspector, - ) async { + Future metaDataFor(RemoteObject remoteObject) async { try { + /// TODO(annagrin): this breaks on changes internal + /// type representation in DDC. Replace by runtime API. + /// https://github.com/dart-lang/sdk/issues/51583 final evalExpression = ''' function(arg) { const sdk = ${globalLoadStrategy.loadModuleSnippet}('dart_sdk'); @@ -183,51 +182,83 @@ class ClassMetaData { const core = sdk.core; const interceptors = sdk._interceptors; const classObject = dart.getReifiedType(arg); - const isFunction = classObject instanceof dart.AbstractFunctionType; - const isRecord = classObject instanceof dart.RecordType; - const isRecordType = dart.is(arg, dart.RecordType); - const isNativeError = dart.is(arg, interceptors.NativeError); + const result = {}; - var name = isFunction ? 'Function' : classObject.name; + var name = classObject.name; result['name'] = name; result['libraryId'] = dart.getLibraryUri(classObject); result['dartName'] = dart.typeName(classObject); - result['isFunction'] = isFunction; - result['isRecord'] = isRecord; - result['isRecordType'] = isRecordType; - result['isNativeError'] = isNativeError; result['length'] = arg['length']; - - if (isRecord) { + result['runtimeKind'] = '${RuntimeObjectKind.object}'; + + if (name == '_HashSet') { + result['runtimeKind'] = '${RuntimeObjectKind.set}'; + } else if (name == 'JSArray') { + result['runtimeKind'] = '${RuntimeObjectKind.list}'; + } else if (name == 'LinkedMap' || name == 'IdentityMap') { + result['runtimeKind'] = '${RuntimeObjectKind.map}'; + } else if (classObject instanceof dart.AbstractFunctionType) { + result['runtimeKind'] = '${RuntimeObjectKind.function}'; + result['name'] = 'Function'; + } else if (classObject instanceof dart.RecordType) { + result['runtimeKind'] = '${RuntimeObjectKind.record}'; result['name'] = 'Record'; var shape = classObject.shape; var positionalCount = shape.positionals; var namedCount = shape.named == null ? 0 : shape.named.length; result['length'] = positionalCount + namedCount; - } - - if (isRecordType) { + } else if (dart.is(arg, dart.RecordType)) { + result['runtimeKind'] = '${RuntimeObjectKind.recordType}'; result['name'] = 'RecordType'; result['length'] = arg.types.length; + } else if (name == 'Type') { + result['runtimeKind'] = '${RuntimeObjectKind.type}'; + } else if (dart.is(arg, dart._Type)) { + result['runtimeKind'] = '${RuntimeObjectKind.wrappedType}'; + } else if (dart.is(arg, interceptors.JavaScriptObject)) { + result['runtimeKind'] = '${RuntimeObjectKind.nativeObject}'; + } else if (dart.is(arg, interceptors.NativeError)) { + result['runtimeKind'] = '${RuntimeObjectKind.nativeError}'; } return result; } '''; - final result = await inspector.jsCallFunctionOn( + final result = await _inspector.jsCallFunctionOn( remoteObject, evalExpression, [remoteObject], returnByValue: true, ); final metadata = result.value as Map; + final jsName = metadata['name']; + final dartName = metadata['dartName']; + final library = metadata['libraryId']; + final runtimeKind = metadata['runtimeKind']; + final length = metadata['length']; + + // We hide internal types for some instance types. + var classRef = classRefFor(library, dartName); + if (runtimeKind == RuntimeObjectKind.record) { + classRef = classRefForRecord; + } else if (runtimeKind == RuntimeObjectKind.recordType) { + classRef = classRefForRecordType; + } else if (runtimeKind == RuntimeObjectKind.wrappedType) { + classRef = classRefForType; + } + + final id = classRef.id; + if (id == null) { + throw StateError('ClassRef id is null for $dartName'); + } + _runtimeObjectKinds[id] = runtimeKind; return ClassMetaData( - jsName: metadata['name'], - length: metadata['length'], - kind: _getInstanceKind(metadata), - classRef: _getClassRef(metadata), + jsName: jsName, + length: length, + runtimeKind: runtimeKind, + classRef: classRef, ); } on ChromeDebugException catch (e, s) { _logger.fine( @@ -238,53 +269,18 @@ class ClassMetaData { return null; } } -} -/// Find instance kind of an object from [metadata]. -String _getInstanceKind(Map metadata) { - final jsName = metadata['name']; - final isFunction = metadata['isFunction']; - final isRecord = metadata['isRecord']; - final isRecordType = metadata['isRecordType']; - - if (jsName == '_HashSet') { - return InstanceKind.kSet; - } - if (jsName == 'JSArray') { - return InstanceKind.kList; - } - if (jsName == 'LinkedMap' || jsName == 'IdentityMap') { - return InstanceKind.kMap; - } - if (isFunction) { - return InstanceKind.kClosure; - } - if (isRecord) { - return InstanceKind.kRecord; - } - if (isRecordType) { - return InstanceKind.kRecordType; + /// Returns true for non-dart JavaScript classes. + bool isNativeJsObject(ClassRef? classRef) { + final id = classRef?.id; + return id != null && + _runtimeObjectKinds[id] == RuntimeObjectKind.nativeObject; } - return InstanceKind.kPlainInstance; -} - -/// Find class ref of an object from [metadata]. -ClassRef _getClassRef(Map metadata) { - final dartName = metadata['dartName']; - final library = metadata['libraryId']; - final isRecord = metadata['isRecord']; - final isRecordType = metadata['isRecordType']; - final isNativeError = metadata['isNativeError']; - if (isRecord) { - return classRefForRecord; - } - if (isRecordType) { - return classRefForRecordType; + /// Returns true for non-dart JavaScript classes. + bool isNativeJsError(ClassRef? classRef) { + final id = classRef?.id; + return id != null && + _runtimeObjectKinds[id] == RuntimeObjectKind.nativeError; } - if (isNativeError) { - return classRefForNativeJsError; - } - - return classRefFor(library, dartName); } diff --git a/dwds/lib/src/services/batched_expression_evaluator.dart b/dwds/lib/src/services/batched_expression_evaluator.dart index cadd0c784..b50e1cdaf 100644 --- a/dwds/lib/src/services/batched_expression_evaluator.dart +++ b/dwds/lib/src/services/batched_expression_evaluator.dart @@ -28,19 +28,19 @@ class EvaluateRequest { class BatchedExpressionEvaluator extends ExpressionEvaluator { final _logger = Logger('BatchedExpressionEvaluator'); - final Debugger _debugger; + final AppInspectorInterface _inspector; final _requestController = BatchedStreamController(delay: 200); bool _closed = false; BatchedExpressionEvaluator( String entrypoint, - AppInspectorInterface inspector, - this._debugger, + this._inspector, + Debugger debugger, Locations locations, Modules modules, ExpressionCompiler compiler, - ) : super(entrypoint, inspector, _debugger, locations, modules, compiler) { + ) : super(entrypoint, _inspector, debugger, locations, modules, compiler) { _requestController.stream.listen(_processRequest); } @@ -148,7 +148,7 @@ class BatchedExpressionEvaluator extends ExpressionEvaluator { request.completer.complete(error); } else { safeUnawaited( - _debugger + _inspector .getProperties( listId, offset: i, diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index 398dfb87a..105cca1bf 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -262,7 +262,6 @@ class ChromeProxyService implements VmServiceInterface { debugger, executionContext, ); - debugger.updateInspector(inspector); final compiler = _compiler; _expressionEvaluator = compiler == null diff --git a/dwds/lib/src/services/expression_evaluator.dart b/dwds/lib/src/services/expression_evaluator.dart index 1e1be6fad..19b45ad3e 100644 --- a/dwds/lib/src/services/expression_evaluator.dart +++ b/dwds/lib/src/services/expression_evaluator.dart @@ -350,7 +350,7 @@ class ExpressionEvaluator { for (var scope in scopeChain) { final objectId = scope.object.objectId; if (objectId != null) { - final scopeProperties = await _debugger.getProperties(objectId); + final scopeProperties = await _inspector.getProperties(objectId); collectVariables(scopeProperties); } } diff --git a/dwds/lib/src/utilities/domain.dart b/dwds/lib/src/utilities/domain.dart index 4d1a896ad..699bd586b 100644 --- a/dwds/lib/src/utilities/domain.dart +++ b/dwds/lib/src/utilities/domain.dart @@ -4,6 +4,7 @@ import 'package:dwds/src/connections/app_connection.dart'; import 'package:dwds/src/debugging/remote_debugger.dart'; +import 'package:dwds/src/utilities/objects.dart'; import 'package:vm_service/vm_service.dart'; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; @@ -91,6 +92,22 @@ abstract class AppInspectorInterface { /// All the scripts in the isolate. Future getScripts(); + /// Calls the Chrome Runtime.getProperties API for the object with [objectId]. + /// + /// Note that the property names are JS names, e.g. + /// Symbol(DartClass.actualName) and will need to be converted. For a system + /// List or Map, [offset] and/or [count] can be provided to indicate a desired + /// range of entries. They will be ignored if there is no [length]. + Future> getProperties( + String objectId, { + int? offset, + int? count, + int? length, + }); + + bool isDisplayableObject(Object? object); + bool isNativeJsError(InstanceRef instanceRef); + /// Return the VM SourceReport for the given parameters. /// /// Currently this implements the 'PossibleBreakpoints' report kind. diff --git a/dwds/test/fixtures/fakes.dart b/dwds/test/fixtures/fakes.dart index fdac99b1a..fadb2ca0b 100644 --- a/dwds/test/fixtures/fakes.dart +++ b/dwds/test/fixtures/fakes.dart @@ -6,11 +6,9 @@ import 'dart:async'; import 'package:dwds/asset_reader.dart'; import 'package:dwds/expression_compiler.dart'; -import 'package:dwds/src/debugging/classes.dart'; import 'package:dwds/src/debugging/execution_context.dart'; import 'package:dwds/src/debugging/inspector.dart'; import 'package:dwds/src/debugging/instance.dart'; -import 'package:dwds/src/debugging/libraries.dart'; import 'package:dwds/src/debugging/metadata/provider.dart'; import 'package:dwds/src/debugging/modules.dart'; import 'package:dwds/src/debugging/remote_debugger.dart'; @@ -63,12 +61,7 @@ class FakeInspector implements AppInspector { RemoteObject({'type': 'string', 'value': 'true'}); @override - Future initialize( - LibraryHelper libraryHelper, - ClassHelper classHelper, - InstanceHelper instanceHelper, - ) async => - {}; + Future initialize() async => {}; @override Future instanceRefFor(Object value) async => diff --git a/dwds/test/inspector_test.dart b/dwds/test/inspector_test.dart index dd2f3fa87..6e66a547c 100644 --- a/dwds/test/inspector_test.dart +++ b/dwds/test/inspector_test.dart @@ -6,7 +6,6 @@ @Timeout(Duration(minutes: 2)) import 'package:dwds/dwds.dart'; -import 'package:dwds/src/debugging/debugger.dart'; import 'package:dwds/src/debugging/inspector.dart'; import 'package:dwds/src/loaders/strategy.dart'; import 'package:dwds/src/utilities/conversions.dart'; @@ -26,13 +25,11 @@ void main() { TestContext(TestProject.testScopesWithSoundNullSafety, provider); late AppInspector inspector; - late Debugger debugger; setUpAll(() async { await context.setUp(); final service = context.service; inspector = service.inspector; - debugger = await service.debuggerFuture; }); tearDownAll(() async { @@ -159,7 +156,7 @@ void main() { test('properties', () async { final remoteObject = await libraryPublicFinal(); - final properties = await debugger.getProperties(remoteObject.objectId!); + final properties = await inspector.getProperties(remoteObject.objectId!); final names = properties.map((p) => p.name).where((x) => x != '__proto__').toList(); final expected = [ diff --git a/dwds/test/instances/instance_inspection_common.dart b/dwds/test/instances/instance_inspection_common.dart index c4c61c388..a76bb2bcc 100644 --- a/dwds/test/instances/instance_inspection_common.dart +++ b/dwds/test/instances/instance_inspection_common.dart @@ -161,41 +161,26 @@ class TestInspector { return instances; } - Future getUnwrappedTypeInstanceRef( + Future getDisplayedRef( String isolateId, - InstanceRef ref, - ) async { - final typeClassId = ref.classRef!.id!; + String instanceId, + ) async => + await service.invoke(isolateId, instanceId, 'toString', []) + as InstanceRef; - // `o.runtimeType` is an instance of `Type`. - expect(await service.getObject(isolateId, typeClassId), matchTypeClass); - - // Get `o.runtimeType._type`. - return (await getFields(isolateId, ref, depth: 1) - as Map)['_type'] as InstanceRef; - } - - Future getUnwrappedTypeInstance( + Future> getDisplayedFields( String isolateId, InstanceRef ref, ) async { - final typeInstanceRef = await getUnwrappedTypeInstanceRef(isolateId, ref); - return await service.getObject(isolateId, typeInstanceRef.id!) as Instance; - } - - Future> getFieldTypes( - String isolateId, - InstanceRef ref, - ) async { - final fieldTypeInstanceRefs = + final fieldRefs = await getFields(isolateId, ref, depth: 1) as Map; - final fieldTypes = await Future.wait( - fieldTypeInstanceRefs.values.map( - (ref) async => await service.invoke(isolateId, ref.id!, 'toString', []), - ), - ); - return fieldTypes.map((ref) => (ref as InstanceRef).valueAsString).toList(); + Future toStringValue(InstanceRef ref) async => + ref.valueAsString ?? + (await getDisplayedRef(isolateId, ref.id!)).valueAsString; + + final fields = await Future.wait(fieldRefs.values.map(toStringValue)); + return fields.toList(); } } @@ -262,14 +247,17 @@ Matcher matchRecordTypeInstance({required int length}) => isA() .having((e) => e.length, 'length', length) .having((e) => e.classRef!, 'classRef', matchRecordTypeClassRef); +Matcher matchTypeStringInstance(String name) => + matchPrimitiveInstance(kind: InstanceKind.kString, value: name); + Matcher matchTypeInstance = isA() .having((e) => e.kind, 'kind', InstanceKind.kPlainInstance) - .having((e) => e.classRef!.name, 'classRef.name', matchTypeClassRef); + .having((e) => e.classRef, 'classRef', matchTypeClassRef); Matcher matchRecordClass = matchClass(libraryId: 'dart:core', type: 'Record'); Matcher matchRecordTypeClass = matchClass(libraryId: 'dart:_runtime', type: 'RecordType'); -Matcher matchTypeClass = matchClass(libraryId: 'dart:_runtime', type: '_Type'); +Matcher matchTypeClass = matchClass(libraryId: 'dart:core', type: 'Type'); Matcher matchClass({required String libraryId, required String type}) => isA() diff --git a/dwds/test/instances/instance_test.dart b/dwds/test/instances/instance_test.dart index a20a8ba42..03e2e4e75 100644 --- a/dwds/test/instances/instance_test.dart +++ b/dwds/test/instances/instance_test.dart @@ -4,7 +4,6 @@ @Timeout(Duration(minutes: 2)) -import 'package:dwds/src/debugging/debugger.dart'; import 'package:dwds/src/debugging/inspector.dart'; import 'package:dwds/src/loaders/strategy.dart'; import 'package:test/test.dart'; @@ -26,14 +25,12 @@ void main() { TestContext(TestProject.testScopesWithSoundNullSafety, provider); late AppInspector inspector; - late Debugger debugger; setUpAll(() async { setCurrentLogWriter(debug: debug); await context.setUp(); final chromeProxyService = context.service; inspector = chromeProxyService.inspector; - debugger = await chromeProxyService.debuggerFuture; }); tearDownAll(() async { @@ -98,7 +95,7 @@ void main() { test('for closure', () async { final remoteObject = await libraryPublicFinal(); - final properties = await debugger.getProperties(remoteObject.objectId!); + final properties = await inspector.getProperties(remoteObject.objectId!); final closure = properties.firstWhere((property) => property.name == 'closure'); final instanceRef = await inspector.instanceRefFor(closure.value!); @@ -149,7 +146,7 @@ void main() { .jsEvaluate(interceptorsNewExpression('JSNoSuchMethodError')); final ref = await inspector.instanceRefFor(remoteObject); expect(ref!.kind, InstanceKind.kPlainInstance); - expect(ref.classRef!.name, 'NativeError'); + expect(ref.classRef!.name, 'JSNoSuchMethodError'); }); }); @@ -185,7 +182,7 @@ void main() { test('for closure', () async { final remoteObject = await libraryPublicFinal(); - final properties = await debugger.getProperties(remoteObject.objectId!); + final properties = await inspector.getProperties(remoteObject.objectId!); final closure = properties.firstWhere((property) => property.name == 'closure'); final instance = await inspector.instanceFor(closure.value!); diff --git a/dwds/test/instances/record_type_inspection_test.dart b/dwds/test/instances/record_type_inspection_test.dart index 78c946cf8..0301f66a3 100644 --- a/dwds/test/instances/record_type_inspection_test.dart +++ b/dwds/test/instances/record_type_inspection_test.dart @@ -57,13 +57,8 @@ Future _runTests({ getInstanceRef(frame, expression) => testInspector.getInstanceRef(isolateId, frame, expression); - getUnwrappedTypeInstanceRef(ref) => - testInspector.getUnwrappedTypeInstanceRef(isolateId, ref); - - getUnwrappedTypeInstance(ref) => - testInspector.getUnwrappedTypeInstance(isolateId, ref); - - getFieldTypes(InstanceRef ref) => testInspector.getFieldTypes(isolateId, ref); + getDisplayedFields(InstanceRef ref) => + testInspector.getDisplayedFields(isolateId, ref); group('$compilationMode |', () { setUpAll(() async { @@ -97,20 +92,17 @@ Future _runTests({ test('simple record type', () async { await onBreakPoint('printSimpleLocalRecord', (event) async { final frame = event.topFrame!.index!; - final typeInstanceRef = - await getInstanceRef(frame, 'record.runtimeType'); - - final ref = await getUnwrappedTypeInstanceRef(typeInstanceRef); - final instance = await getUnwrappedTypeInstance(typeInstanceRef); + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instanceId = instanceRef.id!; - expect(ref, matchRecordTypeInstanceRef(length: 2)); - expect(instance, matchRecordTypeInstance(length: 2)); + expect(instanceRef, matchRecordTypeInstanceRef(length: 2)); + expect(await getObject(instanceId), matchRecordTypeInstance(length: 2)); - final classId = instance.classRef!.id; + final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchRecordTypeClass); expect( - await getFieldTypes(ref), + await getDisplayedFields(instanceRef), ['bool', 'int'], ); }); @@ -136,26 +128,23 @@ Future _runTests({ test('complex record type', () async { await onBreakPoint('printComplexLocalRecord', (event) async { final frame = event.topFrame!.index!; - final typeInstanceRef = - await getInstanceRef(frame, 'record.runtimeType'); - - final ref = await getUnwrappedTypeInstanceRef(typeInstanceRef); - final instance = await getUnwrappedTypeInstance(typeInstanceRef); + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instanceId = instanceRef.id!; - expect(ref, matchRecordTypeInstanceRef(length: 3)); - expect(instance, matchRecordTypeInstance(length: 3)); + expect(instanceRef, matchRecordTypeInstanceRef(length: 3)); + expect(await getObject(instanceId), matchRecordTypeInstance(length: 3)); - final classId = instance.classRef!.id; + final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchRecordTypeClass); expect( - await getFieldTypes(ref), + await getDisplayedFields(instanceRef), ['bool', 'int', 'IdentityMap'], ); }); }); - test('complex records display', () async { + test('complex record type display', () async { await onBreakPoint('printComplexLocalRecord', (event) async { final frame = event.topFrame!.index!; final typeStringRef = @@ -175,20 +164,17 @@ Future _runTests({ test('complex record type with named fields ', () async { await onBreakPoint('printComplexNamedLocalRecord', (event) async { final frame = event.topFrame!.index!; - final typeInstanceRef = - await getInstanceRef(frame, 'record.runtimeType'); - - final ref = await getUnwrappedTypeInstanceRef(typeInstanceRef); - final instance = await getUnwrappedTypeInstance(typeInstanceRef); + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instanceId = instanceRef.id!; - expect(ref, matchRecordTypeInstanceRef(length: 3)); - expect(instance, matchRecordTypeInstance(length: 3)); + expect(instanceRef, matchRecordTypeInstanceRef(length: 3)); + expect(await getObject(instanceId), matchRecordTypeInstance(length: 3)); - final classId = instance.classRef!.id; + final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchRecordTypeClass); expect( - await getFieldTypes(ref), + await getDisplayedFields(instanceRef), ['bool', 'int', 'IdentityMap'], ); }); @@ -211,23 +197,20 @@ Future _runTests({ }); }); - test('nested record type display', () async { + test('nested record type', () async { await onBreakPoint('printNestedLocalRecord', (event) async { final frame = event.topFrame!.index!; - final typeInstanceRef = - await getInstanceRef(frame, 'record.runtimeType'); - - final ref = await getUnwrappedTypeInstanceRef(typeInstanceRef); - final instance = await getUnwrappedTypeInstance(typeInstanceRef); + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instanceId = instanceRef.id!; - expect(ref, matchRecordTypeInstanceRef(length: 2)); - expect(instance, matchRecordTypeInstance(length: 2)); + expect(instanceRef, matchRecordTypeInstanceRef(length: 2)); + expect(await getObject(instanceId), matchRecordTypeInstance(length: 2)); - final classId = instance.classRef!.id; + final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchRecordTypeClass); expect( - await getFieldTypes(ref), + await getDisplayedFields(instanceRef), ['bool', '(bool, int)'], ); }); @@ -253,34 +236,37 @@ Future _runTests({ test('nested record type with named fields', () async { await onBreakPoint('printNestedLocalRecord', (event) async { final frame = event.topFrame!.index!; - final typeInstanceRef = - await getInstanceRef(frame, 'record.runtimeType'); + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instanceId = instanceRef.id!; + final instance = await getObject(instanceId) as Instance; - final ref = await getUnwrappedTypeInstanceRef(typeInstanceRef); - final instance = await getUnwrappedTypeInstance(typeInstanceRef); - - expect(ref, matchRecordTypeInstanceRef(length: 2)); + expect(instanceRef, matchRecordTypeInstanceRef(length: 2)); expect(instance, matchRecordTypeInstance(length: 2)); - final classId = instance.classRef!.id; + final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchRecordTypeClass); expect( - await getFieldTypes(ref), + await getDisplayedFields(instanceRef), ['bool', '(bool, int)'], ); + + final elementTypes = await Future.wait( + instance.fields!.map((e) => getObject((e.value as InstanceRef).id!)), + ); + expect(elementTypes[0], matchTypeInstance); + expect(elementTypes[1], matchRecordTypeInstance(length: 2)); }); }); test('nested record type with named fields display', () async { await onBreakPoint('printNestedNamedLocalRecord', (event) async { final frame = event.topFrame!.index!; - final typeInstanceRef = - await getInstanceRef(frame, 'record.runtimeType'); - final typeInstance = await getObject(typeInstanceRef.id!); - final typeClassId = typeInstance.classRef!.id; + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instance = await getObject(instanceRef.id!); + final typeClassId = instance.classRef!.id; - expect(await getObject(typeClassId), matchTypeClass); + expect(await getObject(typeClassId), matchRecordTypeClass); final typeStringRef = await getInstanceRef(frame, 'record.runtimeType.toString()'); diff --git a/dwds/test/instances/type_inspection_test.dart b/dwds/test/instances/type_inspection_test.dart new file mode 100644 index 000000000..9846aefee --- /dev/null +++ b/dwds/test/instances/type_inspection_test.dart @@ -0,0 +1,325 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@TestOn('vm') +@Timeout(Duration(minutes: 2)) + +import 'package:test/test.dart'; +import 'package:test_common/logging.dart'; +import 'package:test_common/test_sdk_configuration.dart'; +import 'package:vm_service/vm_service.dart'; + +import '../fixtures/context.dart'; +import '../fixtures/project.dart'; +import 'instance_inspection_common.dart'; + +void main() async { + // Enable verbose logging for debugging. + final debug = false; + + final provider = TestSdkConfigurationProvider(verbose: debug); + tearDownAll(provider.dispose); + + for (var compilationMode in CompilationMode.values) { + await _runTests( + provider: provider, + compilationMode: compilationMode, + debug: debug, + ); + } +} + +Future _runTests({ + required TestSdkConfigurationProvider provider, + required CompilationMode compilationMode, + required bool debug, +}) async { + final context = + TestContext(TestProject.testExperimentWithSoundNullSafety, provider); + final testInspector = TestInspector(context); + + late VmServiceInterface service; + late Stream stream; + late String isolateId; + late ScriptRef mainScript; + + onBreakPoint(breakPointId, body) => testInspector.onBreakPoint( + stream, + isolateId, + mainScript, + breakPointId, + body, + ); + + getObject(instanceId) => service.getObject(isolateId, instanceId); + + getDisplayedRef(instanceId) => + testInspector.getDisplayedRef(isolateId, instanceId); + + getDisplayedFields(instanceRef) => + testInspector.getDisplayedFields(isolateId, instanceRef); + + getInstanceRef(frame, expression) => + testInspector.getInstanceRef(isolateId, frame, expression); + + group('$compilationMode |', () { + setUpAll(() async { + setCurrentLogWriter(debug: debug); + await context.setUp( + compilationMode: compilationMode, + enableExpressionEvaluation: true, + verboseCompiler: debug, + experiments: ['records', 'patterns'], + ); + service = context.debugConnection.vmService; + + final vm = await service.getVM(); + isolateId = vm.isolates!.first.id!; + final scripts = await service.getScripts(isolateId); + + await service.streamListen('Debug'); + stream = service.onEvent('Debug'); + + mainScript = scripts.scripts! + .firstWhere((each) => each.uri!.contains('main.dart')); + }); + + tearDownAll(() async { + await context.tearDown(); + }); + + setUp(() => setCurrentLogWriter(debug: debug)); + tearDown(() => service.resume(isolateId)); + + test('String type', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, "'1'.runtimeType"); + final instanceId = instanceRef.id!; + + final instance = await getObject(instanceId) as Instance; + + expect(instance, matchTypeInstance); + + final classId = instanceRef.classRef!.id; + expect(await getObject(classId), matchTypeClass); + }); + }); + + test('String type display', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, "'1'.runtimeType"); + final instanceId = instanceRef.id!; + final displayedRef = await getDisplayedRef(instanceId); + final displayedInstance = await getObject(displayedRef.id); + + expect(displayedInstance, matchTypeStringInstance('String')); + expect( + await getDisplayedFields(instanceRef), + [matches('[0-9]*'), 'Type'], + ); + }); + }); + + test('int type', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, '1.runtimeType'); + final instanceId = instanceRef.id!; + + final instance = await getObject(instanceId); + expect(instance, matchTypeInstance); + + final classId = instanceRef.classRef!.id; + expect(await getObject(classId), matchTypeClass); + }); + }); + + test('int type display', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, '1.runtimeType'); + final instanceId = instanceRef.id!; + + final displayedRef = await getDisplayedRef(instanceId); + final displayedInstance = await getObject(displayedRef.id); + + expect(displayedInstance, matchTypeStringInstance('int')); + expect( + await getDisplayedFields(instanceRef), + [matches('[0-9]*'), 'Type'], + ); + }); + }); + + test('list type', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, '[].runtimeType'); + final instanceId = instanceRef.id!; + + final instance = await getObject(instanceId); + expect(instance, matchTypeInstance); + + final classId = instanceRef.classRef!.id; + expect(await getObject(classId), matchTypeClass); + }); + }); + + test('list type display', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, '[].runtimeType'); + final instanceId = instanceRef.id!; + + final displayedRef = await getDisplayedRef(instanceId); + final displayedInstance = await getObject(displayedRef.id); + + expect(displayedInstance, matchTypeStringInstance('List')); + expect( + await getDisplayedFields(instanceRef), + [matches('[0-9]*'), 'Type'], + ); + }); + }); + + test('map type', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = + await getInstanceRef(frame, '{}.runtimeType'); + final instanceId = instanceRef.id!; + + final instance = await getObject(instanceId); + expect(instance, matchTypeInstance); + + final classId = instanceRef.classRef!.id; + expect(await getObject(classId), matchTypeClass); + }); + }); + + test('map type display', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = + await getInstanceRef(frame, '{}.runtimeType'); + final instanceId = instanceRef.id!; + + final displayedRef = await getDisplayedRef(instanceId); + final displayedInstance = await getObject(displayedRef.id); + + expect( + displayedInstance, + matchTypeStringInstance('IdentityMap'), + ); + expect( + await getDisplayedFields(instanceRef), + [matches('[0-9]*'), 'Type'], + ); + }); + }); + + test('set type', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, '{}.runtimeType'); + final instanceId = instanceRef.id!; + + final instance = await getObject(instanceId); + expect(instance, matchTypeInstance); + + final classId = instanceRef.classRef!.id; + expect(await getObject(classId), matchTypeClass); + }); + }); + + test('set type display', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, '{}.runtimeType'); + final instanceId = instanceRef.id!; + + final displayedRef = await getDisplayedRef(instanceId); + final displayedInstance = await getObject(displayedRef.id); + + expect( + displayedInstance, + matchTypeStringInstance('_IdentityHashSet'), + ); + expect( + await getDisplayedFields(instanceRef), + [matches('[0-9]*'), 'Type'], + ); + }); + }); + + test('record type', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, "(0,'a').runtimeType"); + final instanceId = instanceRef.id!; + + final instance = await getObject(instanceId); + expect(instance, matchRecordTypeInstance(length: 2)); + + final classId = instanceRef.classRef!.id; + expect(await getObject(classId), matchRecordTypeClass); + }); + }); + + test('record type display', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, "(0,'a').runtimeType"); + final instanceId = instanceRef.id!; + + final displayedRef = await getDisplayedRef(instanceId); + final displayedInstance = await getObject(displayedRef.id); + + expect( + displayedInstance, + matchTypeStringInstance('(int, String)'), + ); + expect( + await getDisplayedFields(instanceRef), + ['int', 'String'], + ); + }); + }); + + test('class type', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = + await getInstanceRef(frame, "Uri.file('').runtimeType"); + final instanceId = instanceRef.id!; + + final instance = await getObject(instanceId); + expect(instance, matchTypeInstance); + + final classId = instanceRef.classRef!.id; + expect(await getObject(classId), matchTypeClass); + }); + }); + + test('class type display', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = + await getInstanceRef(frame, "Uri.file('').runtimeType"); + final instanceId = instanceRef.id!; + + final displayedRef = await getDisplayedRef(instanceId); + final displayedInstance = await getObject(displayedRef.id); + + expect(displayedInstance, matchTypeStringInstance('_Uri')); + expect( + await getDisplayedFields(instanceRef), + [matches('[0-9]*'), 'Type'], + ); + }); + }); + }); +} diff --git a/dwds/test/metadata/class_test.dart b/dwds/test/metadata/class_test.dart index dce372729..5b44890f4 100644 --- a/dwds/test/metadata/class_test.dart +++ b/dwds/test/metadata/class_test.dart @@ -6,36 +6,25 @@ import 'package:dwds/src/debugging/metadata/class.dart'; import 'package:test/test.dart'; -import 'package:vm_service/vm_service.dart'; void main() { test('Gracefully handles invalid length objects', () async { - var metadata = ClassMetaData( - length: null, - kind: InstanceKind.kPlainInstance, - classRef: classRefForUnknown, - ); + createMetadata(dynamic length) => ClassMetaData( + length: length, + runtimeKind: RuntimeObjectKind.object, + classRef: classRefForUnknown, + ); + + var metadata = createMetadata(null); expect(metadata.length, isNull); - metadata = ClassMetaData( - length: {}, - kind: InstanceKind.kPlainInstance, - classRef: classRefForUnknown, - ); + metadata = createMetadata({}); expect(metadata.length, isNull); - metadata = ClassMetaData( - length: '{}', - kind: InstanceKind.kPlainInstance, - classRef: classRefForUnknown, - ); + metadata = createMetadata('{}'); expect(metadata.length, isNull); - metadata = ClassMetaData( - length: 0, - kind: InstanceKind.kPlainInstance, - classRef: classRefForUnknown, - ); + metadata = createMetadata(0); expect(metadata.length, equals(0)); }); } From bacc5b5d518a23416ab788d3c7b51bcb2fbb42d0 Mon Sep 17 00:00:00 2001 From: Anna Gringauze Date: Fri, 28 Apr 2023 17:31:01 -0700 Subject: [PATCH 06/13] update ref in changelod --- dwds/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index 4cc1af707..19956d899 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -3,7 +3,7 @@ - Do not show async frame errors on evaluation. - [#2073](https://github.com/dart-lang/webdev/pull/2073) - Refactor code for presenting record instances. - [#2074](https://github.com/dart-lang/webdev/pull/2074) - Display record types concisely. - [#2070](https://github.com/dart-lang/webdev/pull/2070) -- Simplify class metadata. - [#2102](https://github.com/dart-lang/webdev/pull/2102) +- Display type objects concisely. - [#2103](https://github.com/dart-lang/webdev/pull/2103) ## 19.0.0 From 14994b934360b894369583c53ce0e5ee9f6f7f3a Mon Sep 17 00:00:00 2001 From: Anna Gringauze Date: Tue, 2 May 2023 11:50:21 -0700 Subject: [PATCH 07/13] Fix tests --- dwds/lib/src/debugging/metadata/class.dart | 4 +- dwds/test/debugger_test.dart | 6 +- dwds/test/evaluate_common.dart | 37 ++++------ dwds/test/expression_evaluator_test.dart | 3 +- dwds/test/fixtures/fakes.dart | 30 +++++++- .../instances/instance_inspection_common.dart | 25 ++++--- dwds/test/instances/instance_test.dart | 32 +++++++++ .../record_type_inspection_test.dart | 69 +++++++++++++++++-- dwds/test/instances/type_inspection_test.dart | 62 ++++++++++------- 9 files changed, 197 insertions(+), 71 deletions(-) diff --git a/dwds/lib/src/debugging/metadata/class.dart b/dwds/lib/src/debugging/metadata/class.dart index 70e83d99e..b85d95b3f 100644 --- a/dwds/lib/src/debugging/metadata/class.dart +++ b/dwds/lib/src/debugging/metadata/class.dart @@ -216,10 +216,10 @@ class ClassMetaDataHelper { result['runtimeKind'] = '${RuntimeObjectKind.type}'; } else if (dart.is(arg, dart._Type)) { result['runtimeKind'] = '${RuntimeObjectKind.wrappedType}'; - } else if (dart.is(arg, interceptors.JavaScriptObject)) { - result['runtimeKind'] = '${RuntimeObjectKind.nativeObject}'; } else if (dart.is(arg, interceptors.NativeError)) { result['runtimeKind'] = '${RuntimeObjectKind.nativeError}'; + } else if (dart.is(arg, interceptors.JavaScriptObject)) { + result['runtimeKind'] = '${RuntimeObjectKind.nativeObject}'; } return result; } diff --git a/dwds/test/debugger_test.dart b/dwds/test/debugger_test.dart index 58aa29a43..bac31b50f 100644 --- a/dwds/test/debugger_test.dart +++ b/dwds/test/debugger_test.dart @@ -98,7 +98,7 @@ void main() async { skipLists, root, ); - inspector = FakeInspector(fakeIsolate: simpleIsolate); + inspector = FakeInspector(webkitDebugger, fakeIsolate: simpleIsolate); debugger.updateInspector(inspector); }); @@ -168,7 +168,7 @@ void main() async { setUp(() { // We need to provide an Isolate so that the code doesn't bail out on a null // check before it has a chance to throw. - inspector = FakeInspector(fakeIsolate: simpleIsolate); + inspector = FakeInspector(webkitDebugger, fakeIsolate: simpleIsolate); debugger.updateInspector(inspector); }); @@ -176,7 +176,7 @@ void main() async { setUp(() { // We need to provide an Isolate so that the code doesn't bail out on a null // check before it has a chance to throw. - inspector = FakeInspector(fakeIsolate: simpleIsolate); + inspector = FakeInspector(webkitDebugger, fakeIsolate: simpleIsolate); debugger.updateInspector(inspector); }); diff --git a/dwds/test/evaluate_common.dart b/dwds/test/evaluate_common.dart index 7f0ee77b1..aefb331f6 100644 --- a/dwds/test/evaluate_common.dart +++ b/dwds/test/evaluate_common.dart @@ -284,32 +284,25 @@ void testAll({ final instanceRef = result as InstanceRef; // Type - var instance = await getInstance(instanceRef); + final instance = await getInstance(instanceRef); for (var field in instance.fields!) { final name = field.decl!.name; + final fieldInstance = + await getInstance(field.value as InstanceRef); - // Type. (i.e. Type._type) - instance = await getInstance(field.value as InstanceRef); - for (var field in instance.fields!) { - final nestedName = field.decl!.name; - - // Type.. (i.e Type._type._subtypeCache) - instance = await getInstance(field.value as InstanceRef); - - expect( - instance, - isA().having( - (instance) => instance.classRef!.name, - 'Type.$name.$nestedName: classRef.name', - isNot( - isIn([ - 'NativeJavaScriptObject', - 'JavaScriptObject', - ]), - ), + expect( + fieldInstance, + isA().having( + (i) => i.classRef!.name, + 'Type.$name: classRef.name', + isNot( + isIn([ + 'NativeJavaScriptObject', + 'JavaScriptObject', + ]), ), - ); - } + ), + ); } }); }); diff --git a/dwds/test/expression_evaluator_test.dart b/dwds/test/expression_evaluator_test.dart index 908a1741b..e934d8366 100644 --- a/dwds/test/expression_evaluator_test.dart +++ b/dwds/test/expression_evaluator_test.dart @@ -58,7 +58,8 @@ void main() async { skipLists, root, ); - final inspector = FakeInspector(fakeIsolate: simpleIsolate); + final inspector = + FakeInspector(webkitDebugger, fakeIsolate: simpleIsolate); debugger.updateInspector(inspector); _evaluator = ExpressionEvaluator( diff --git a/dwds/test/fixtures/fakes.dart b/dwds/test/fixtures/fakes.dart index fadb2ca0b..e51ecc501 100644 --- a/dwds/test/fixtures/fakes.dart +++ b/dwds/test/fixtures/fakes.dart @@ -16,6 +16,7 @@ import 'package:dwds/src/debugging/webkit_debugger.dart'; import 'package:dwds/src/handlers/socket_connections.dart'; import 'package:dwds/src/loaders/require.dart'; import 'package:dwds/src/loaders/strategy.dart'; +import 'package:dwds/src/utilities/objects.dart'; import 'package:shelf/shelf.dart' as shelf; import 'package:vm_service/vm_service.dart'; @@ -44,7 +45,8 @@ Isolate get simpleIsolate => Isolate( ); class FakeInspector implements AppInspector { - FakeInspector({required this.fakeIsolate}); + final WebkitDebugger _remoteDebugger; + FakeInspector(this._remoteDebugger, {required this.fakeIsolate}); Isolate fakeIsolate; @@ -91,6 +93,32 @@ class FakeInspector implements AppInspector { name: fakeIsolate.name, isSystemIsolate: fakeIsolate.isSystemIsolate, ); + + @override + Future> getProperties( + String objectId, { + int? offset, + int? count, + int? length, + }) async { + final response = await _remoteDebugger.sendCommand( + 'Runtime.getProperties', + params: { + 'objectId': objectId, + 'ownProperties': true, + }, + ); + final result = response.result?['result']; + return result + .map((each) => Property(each as Map)) + .toList(); + } + + @override + bool isDisplayableObject(Object? object) => true; + + @override + bool isNativeJsError(InstanceRef instanceRef) => false; } class FakeSseConnection implements SseSocketConnection { diff --git a/dwds/test/instances/instance_inspection_common.dart b/dwds/test/instances/instance_inspection_common.dart index a76bb2bcc..ad956f8cb 100644 --- a/dwds/test/instances/instance_inspection_common.dart +++ b/dwds/test/instances/instance_inspection_common.dart @@ -65,16 +65,12 @@ class TestInspector { expect(result, isA()); final instance = result as Instance; - // TODO(annagrin): we sometimes get mismatching reference - // and instance kinds from chrome. Investigate. - if (instanceRef.kind != InstanceKind.kClosure) { - expect( - instance.kind, - instanceRef.kind, - reason: 'object $instanceId with ref kind ${instanceRef.kind} ' - 'has an instance kind ${instance.kind}', - ); - } + expect( + instance.kind, + instanceRef.kind, + reason: 'object $instanceId with ref kind ${instanceRef.kind} ' + 'has an instance kind ${instance.kind}', + ); final fields = instance.fields; final associations = instance.associations; @@ -213,6 +209,15 @@ Matcher matchRecordTypeInstanceRef({required int length}) => isA() .having((e) => e.length, 'length', length) .having((e) => e.classRef!, 'classRef', matchRecordTypeClassRef); +Matcher matchTypeInstanceRef = isA() + .having((e) => e.kind, 'kind', InstanceKind.kPlainInstance) + .having((e) => e.classRef, 'classRef', matchTypeClassRef); + +Matcher matchPrimitiveInstanceRef({ + required String kind, +}) => + isA().having((e) => e.kind, 'kind', kind); + Matcher matchPrimitiveInstance({ required String kind, required dynamic value, diff --git a/dwds/test/instances/instance_test.dart b/dwds/test/instances/instance_test.dart index 03e2e4e75..8e246b691 100644 --- a/dwds/test/instances/instance_test.dart +++ b/dwds/test/instances/instance_test.dart @@ -148,6 +148,14 @@ void main() { expect(ref!.kind, InstanceKind.kPlainInstance); expect(ref.classRef!.name, 'JSNoSuchMethodError'); }); + + test('for a native JavaScript object', () async { + final remoteObject = await inspector + .jsEvaluate(interceptorsNewExpression('LegacyJavaScriptObject')); + final ref = await inspector.instanceRefFor(remoteObject); + expect(ref!.kind, InstanceKind.kPlainInstance); + expect(ref.classRef!.name, 'LegacyJavaScriptObject'); + }); }); group('instance', () { @@ -250,5 +258,29 @@ void main() { final field = instance.fields!.first; expect(field.decl!.name, '_internal'); }); + + test('for a native JavaScript error', () async { + final remoteObject = + await inspector.jsEvaluate(interceptorsNewExpression('NativeError')); + final ref = await inspector.instanceFor(remoteObject); + expect(ref!.kind, InstanceKind.kPlainInstance); + expect(ref.classRef!.name, 'NativeError'); + }); + + test('for a native JavaScript type error', () async { + final remoteObject = await inspector + .jsEvaluate(interceptorsNewExpression('JSNoSuchMethodError')); + final ref = await inspector.instanceFor(remoteObject); + expect(ref!.kind, InstanceKind.kPlainInstance); + expect(ref.classRef!.name, 'JSNoSuchMethodError'); + }); + + test('for a native JavaScript object', () async { + final remoteObject = await inspector + .jsEvaluate(interceptorsNewExpression('LegacyJavaScriptObject')); + final ref = await inspector.instanceFor(remoteObject); + expect(ref!.kind, InstanceKind.kPlainInstance); + expect(ref.classRef!.name, 'LegacyJavaScriptObject'); + }); }); } diff --git a/dwds/test/instances/record_type_inspection_test.dart b/dwds/test/instances/record_type_inspection_test.dart index 0301f66a3..33fdccd6a 100644 --- a/dwds/test/instances/record_type_inspection_test.dart +++ b/dwds/test/instances/record_type_inspection_test.dart @@ -60,6 +60,15 @@ Future _runTests({ getDisplayedFields(InstanceRef ref) => testInspector.getDisplayedFields(isolateId, ref); + Future> getElements(String instanceId) async { + final instance = await getObject(instanceId) as Instance; + return Future.wait( + instance.fields!.map( + (e) async => await getObject((e.value as InstanceRef).id!) as Instance, + ), + ); + } + group('$compilationMode |', () { setUpAll(() async { setCurrentLogWriter(debug: debug); @@ -108,6 +117,19 @@ Future _runTests({ }); }); + test('simple record type elements', () async { + await onBreakPoint('printSimpleLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instanceId = instanceRef.id!; + + expect( + await getElements(instanceId), + [matchTypeInstance, matchTypeInstance], + ); + }); + }); + test('simple record type display', () async { await onBreakPoint('printSimpleLocalRecord', (event) async { final frame = event.topFrame!.index!; @@ -144,6 +166,19 @@ Future _runTests({ }); }); + test('complex record type elements', () async { + await onBreakPoint('printComplexLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instanceId = instanceRef.id!; + + expect( + await getElements(instanceId), + [matchTypeInstance, matchTypeInstance, matchTypeInstance], + ); + }); + }); + test('complex record type display', () async { await onBreakPoint('printComplexLocalRecord', (event) async { final frame = event.topFrame!.index!; @@ -213,6 +248,29 @@ Future _runTests({ await getDisplayedFields(instanceRef), ['bool', '(bool, int)'], ); + final elements = await getElements(instanceId); + expect( + await getDisplayedFields(elements[1]), + ['bool', 'int'], + ); + }); + }); + + test('nested record type elements', () async { + await onBreakPoint('printNestedLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instanceId = instanceRef.id!; + + final elements = await getElements(instanceId); + expect( + elements, + [matchTypeInstance, matchRecordTypeInstance(length: 2)], + ); + expect( + await getElements(elements[1].id!), + [matchTypeInstance, matchTypeInstance], + ); }); }); @@ -238,7 +296,7 @@ Future _runTests({ final frame = event.topFrame!.index!; final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); final instanceId = instanceRef.id!; - final instance = await getObject(instanceId) as Instance; + final instance = await getObject(instanceId); expect(instanceRef, matchRecordTypeInstanceRef(length: 2)); expect(instance, matchRecordTypeInstance(length: 2)); @@ -250,12 +308,11 @@ Future _runTests({ await getDisplayedFields(instanceRef), ['bool', '(bool, int)'], ); - - final elementTypes = await Future.wait( - instance.fields!.map((e) => getObject((e.value as InstanceRef).id!)), + final elements = await getElements(instanceId); + expect( + await getDisplayedFields(elements[1]), + ['bool', 'int'], ); - expect(elementTypes[0], matchTypeInstance); - expect(elementTypes[1], matchRecordTypeInstance(length: 2)); }); }); diff --git a/dwds/test/instances/type_inspection_test.dart b/dwds/test/instances/type_inspection_test.dart index 9846aefee..7f1c99b79 100644 --- a/dwds/test/instances/type_inspection_test.dart +++ b/dwds/test/instances/type_inspection_test.dart @@ -63,6 +63,25 @@ Future _runTests({ getInstanceRef(frame, expression) => testInspector.getInstanceRef(isolateId, frame, expression); + getFields(instanceRef, {offset, count, depth = -1}) => + testInspector.getFields( + isolateId, + instanceRef, + offset: offset, + count: count, + depth: depth, + ); + + final matchTypeObject = { + 'hashCode': matchPrimitiveInstanceRef(kind: InstanceKind.kDouble), + 'runtimeType': matchTypeInstanceRef, + }; + + final matchDisplayedTypeObject = [ + matches('[0-9]*'), + 'Type', + ]; + group('$compilationMode |', () { setUpAll(() async { setCurrentLogWriter(debug: debug); @@ -98,12 +117,12 @@ Future _runTests({ final instanceRef = await getInstanceRef(frame, "'1'.runtimeType"); final instanceId = instanceRef.id!; - final instance = await getObject(instanceId) as Instance; - + final instance = await getObject(instanceId); expect(instance, matchTypeInstance); final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); + expect(await getFields(instanceRef, depth: 1), matchTypeObject); }); }); @@ -116,10 +135,7 @@ Future _runTests({ final displayedInstance = await getObject(displayedRef.id); expect(displayedInstance, matchTypeStringInstance('String')); - expect( - await getDisplayedFields(instanceRef), - [matches('[0-9]*'), 'Type'], - ); + expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); }); }); @@ -134,6 +150,7 @@ Future _runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); + expect(await getFields(instanceRef, depth: 1), matchTypeObject); }); }); @@ -147,10 +164,7 @@ Future _runTests({ final displayedInstance = await getObject(displayedRef.id); expect(displayedInstance, matchTypeStringInstance('int')); - expect( - await getDisplayedFields(instanceRef), - [matches('[0-9]*'), 'Type'], - ); + expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); }); }); @@ -165,6 +179,7 @@ Future _runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); + expect(await getFields(instanceRef, depth: 1), matchTypeObject); }); }); @@ -178,10 +193,7 @@ Future _runTests({ final displayedInstance = await getObject(displayedRef.id); expect(displayedInstance, matchTypeStringInstance('List')); - expect( - await getDisplayedFields(instanceRef), - [matches('[0-9]*'), 'Type'], - ); + expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); }); }); @@ -197,6 +209,7 @@ Future _runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); + expect(await getFields(instanceRef, depth: 1), matchTypeObject); }); }); @@ -214,10 +227,7 @@ Future _runTests({ displayedInstance, matchTypeStringInstance('IdentityMap'), ); - expect( - await getDisplayedFields(instanceRef), - [matches('[0-9]*'), 'Type'], - ); + expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); }); }); @@ -232,6 +242,7 @@ Future _runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); + expect(await getFields(instanceRef, depth: 1), matchTypeObject); }); }); @@ -248,10 +259,7 @@ Future _runTests({ displayedInstance, matchTypeStringInstance('_IdentityHashSet'), ); - expect( - await getDisplayedFields(instanceRef), - [matches('[0-9]*'), 'Type'], - ); + expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); }); }); @@ -266,6 +274,10 @@ Future _runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchRecordTypeClass); + expect( + await getFields(instanceRef, depth: 2), + {1: matchTypeObject, 2: matchTypeObject}, + ); }); }); @@ -301,6 +313,7 @@ Future _runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); + expect(await getFields(instanceRef, depth: 1), matchTypeObject); }); }); @@ -315,10 +328,7 @@ Future _runTests({ final displayedInstance = await getObject(displayedRef.id); expect(displayedInstance, matchTypeStringInstance('_Uri')); - expect( - await getDisplayedFields(instanceRef), - [matches('[0-9]*'), 'Type'], - ); + expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); }); }); }); From deca9a371b830620301b93b553240e4703129bc5 Mon Sep 17 00:00:00 2001 From: Anna Gringauze Date: Tue, 2 May 2023 16:45:15 -0700 Subject: [PATCH 08/13] Fix inadvertent changes --- dwds/lib/src/debugging/debugger.dart | 2 -- fixtures/_testPackageSound/pubspec.yaml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dwds/lib/src/debugging/debugger.dart b/dwds/lib/src/debugging/debugger.dart index 4812ab85b..d85bb49ad 100644 --- a/dwds/lib/src/debugging/debugger.dart +++ b/dwds/lib/src/debugging/debugger.dart @@ -46,8 +46,6 @@ class Debugger extends Domain { final SkipLists _skipLists; final String _root; - //final classMetaDataHelper = ClassMetaDataHelper(); - Debugger._( this._remoteDebugger, this._streamNotify, diff --git a/fixtures/_testPackageSound/pubspec.yaml b/fixtures/_testPackageSound/pubspec.yaml index c7d473804..4207b8667 100644 --- a/fixtures/_testPackageSound/pubspec.yaml +++ b/fixtures/_testPackageSound/pubspec.yaml @@ -12,6 +12,6 @@ dependencies: path: ../_testSound dev_dependencies: - build_runner: 2.4.0 + build_runner: ^2.4.0 build_web_compilers: ^4.0.0 From 11467d9c26430fed4349067b6d53c963cf94df66 Mon Sep 17 00:00:00 2001 From: Anna Gringauze Date: Wed, 3 May 2023 16:29:22 -0700 Subject: [PATCH 09/13] Return InstanceKind.kType instances for type objects to match VM --- dwds/lib/src/debugging/inspector.dart | 2 +- dwds/lib/src/debugging/instance.dart | 90 +++++------ dwds/lib/src/debugging/metadata/class.dart | 15 +- .../instances/instance_inspection_common.dart | 33 +++- .../record_type_inspection_test.dart | 95 ++++++++---- dwds/test/instances/type_inspection_test.dart | 146 ++++-------------- 6 files changed, 168 insertions(+), 213 deletions(-) diff --git a/dwds/lib/src/debugging/inspector.dart b/dwds/lib/src/debugging/inspector.dart index 40f95a9a2..6fc6829a8 100644 --- a/dwds/lib/src/debugging/inspector.dart +++ b/dwds/lib/src/debugging/inspector.dart @@ -693,7 +693,7 @@ class AppInspector implements AppInspectorInterface { .isNativeJsObject(instanceRef.classRef); } - /// Returns true of JavaScript exceptions. + /// Returns true for JavaScript exceptions. @override bool isNativeJsError(InstanceRef instanceRef) { return _instanceHelper.metadataHelper.isNativeJsError(instanceRef.classRef); diff --git a/dwds/lib/src/debugging/instance.dart b/dwds/lib/src/debugging/instance.dart index 93d6db487..dd2988ec9 100644 --- a/dwds/lib/src/debugging/instance.dart +++ b/dwds/lib/src/debugging/instance.dart @@ -125,55 +125,48 @@ class InstanceHelper extends Domain { return _closureInstanceFor(remoteObject); case RuntimeObjectKind.list: return await _listInstanceFor( - classRef, + metaData, remoteObject, offset: offset, count: count, - length: metaData.length, ); case RuntimeObjectKind.set: return await _setInstanceFor( - classRef, + metaData, remoteObject, offset: offset, count: count, - length: metaData.length, ); case RuntimeObjectKind.map: return await _mapInstanceFor( - classRef, + metaData, remoteObject, offset: offset, count: count, - length: metaData.length, ); case RuntimeObjectKind.record: return await _recordInstanceFor( - classRef, + metaData, remoteObject, offset: offset, count: count, - length: metaData.length, ); case RuntimeObjectKind.recordType: return await _recordTypeInstanceFor( - classRef, + metaData, remoteObject, offset: offset, count: count, - length: metaData.length, ); case RuntimeObjectKind.type: return await _plainTypeInstanceFor( - classRef, + metaData, remoteObject, offset: offset, count: count, - length: metaData.length, ); case RuntimeObjectKind.wrappedType: return await _wrappedTypeInstanceFor( - classRef, remoteObject, offset: offset, count: count, @@ -183,11 +176,10 @@ class InstanceHelper extends Domain { case RuntimeObjectKind.nativeObject: default: return await _plainInstanceFor( - classRef, + metaData, remoteObject, offset: offset, count: count, - length: metaData.length, ); } } @@ -241,11 +233,10 @@ class InstanceHelper extends Domain { /// Create a plain instance of [classRef] from [remoteObject] and the JS /// properties [properties]. Future _plainInstanceFor( - ClassRef classRef, + ClassMetaData metaData, RemoteObject remoteObject, { int? offset, int? count, - int? length, }) async { final objectId = remoteObject.objectId; if (objectId == null) return null; @@ -254,11 +245,12 @@ class InstanceHelper extends Domain { objectId, offset: offset, count: count, - length: length, + length: metaData.length, ); final dartProperties = await _dartFieldsFor(properties, remoteObject); var boundFields = await Future.wait( - dartProperties.map>((p) => _fieldFor(p, classRef)), + dartProperties + .map>((p) => _fieldFor(p, metaData.classRef)), ); boundFields = boundFields .where((bv) => inspector.isDisplayableObject(bv.value)) @@ -268,7 +260,7 @@ class InstanceHelper extends Domain { kind: InstanceKind.kPlainInstance, id: objectId, identityHashCode: remoteObject.objectId.hashCode, - classRef: classRef, + classRef: metaData.classRef, )..fields = boundFields; return result; } @@ -340,11 +332,10 @@ class InstanceHelper extends Domain { /// [length] is the expected length of the whole object, read from /// the [ClassMetaData]. Future _mapInstanceFor( - ClassRef classRef, + ClassMetaData metaData, RemoteObject remoteObject, { int? offset, int? count, - int? length, }) async { final objectId = remoteObject.objectId; if (objectId == null) return null; @@ -355,15 +346,15 @@ class InstanceHelper extends Domain { final rangeCount = _calculateRangeCount( count: count, elementCount: associations.length, - length: length, + length: metaData.length, ); return Instance( identityHashCode: remoteObject.objectId.hashCode, kind: InstanceKind.kMap, id: objectId, - classRef: classRef, + classRef: metaData.classRef, ) - ..length = length + ..length = metaData.length ..offset = offset ..count = rangeCount ..associations = associations; @@ -379,11 +370,10 @@ class InstanceHelper extends Domain { /// [length] is the expected length of the whole object, read from /// the [ClassMetaData]. Future _listInstanceFor( - ClassRef classRef, + ClassMetaData metaData, RemoteObject remoteObject, { int? offset, int? count, - int? length, }) async { final objectId = remoteObject.objectId; if (objectId == null) return null; @@ -392,20 +382,20 @@ class InstanceHelper extends Domain { remoteObject, offset: offset, count: count, - length: length, + length: metaData.length, ); final rangeCount = _calculateRangeCount( count: count, elementCount: elements.length, - length: length, + length: metaData.length, ); return Instance( identityHashCode: remoteObject.objectId.hashCode, kind: InstanceKind.kList, id: objectId, - classRef: classRef, + classRef: metaData.classRef, ) - ..length = length + ..length = metaData.length ..elements = elements ..offset = offset ..count = rangeCount; @@ -590,11 +580,10 @@ class InstanceHelper extends Domain { /// [length] is the expected length of the whole object, read from /// the [ClassMetaData]. Future _recordInstanceFor( - ClassRef classRef, + ClassMetaData metaData, RemoteObject remoteObject, { int? offset, int? count, - int? length, }) async { final objectId = remoteObject.objectId; if (objectId == null) return null; @@ -604,15 +593,15 @@ class InstanceHelper extends Domain { final rangeCount = _calculateRangeCount( count: count, elementCount: fields.length, - length: length, + length: metaData.length, ); return Instance( identityHashCode: remoteObject.objectId.hashCode, kind: InstanceKind.kRecord, id: objectId, - classRef: classRef, + classRef: metaData.classRef, ) - ..length = length + ..length = metaData.length ..offset = offset ..count = rangeCount ..fields = fields; @@ -628,11 +617,10 @@ class InstanceHelper extends Domain { /// [length] is the expected length of the whole object, read from /// the [ClassMetaData]. Future _recordTypeInstanceFor( - ClassRef classRef, + ClassMetaData metaData, RemoteObject remoteObject, { int? offset, int? count, - int? length, }) async { final objectId = remoteObject.objectId; if (objectId == null) return null; @@ -642,15 +630,15 @@ class InstanceHelper extends Domain { final rangeCount = _calculateRangeCount( count: count, elementCount: fields.length, - length: length, + length: metaData.length, ); return Instance( identityHashCode: remoteObject.objectId.hashCode, kind: InstanceKind.kRecordType, id: objectId, - classRef: classRef, + classRef: metaData.classRef, ) - ..length = length + ..length = metaData.length ..offset = offset ..count = rangeCount ..fields = fields; @@ -702,12 +690,12 @@ class InstanceHelper extends Domain { } Future _setInstanceFor( - ClassRef classRef, + ClassMetaData metaData, RemoteObject remoteObject, { int? offset, int? count, - int? length, }) async { + final length = metaData.length; final objectId = remoteObject.objectId; if (objectId == null) return null; @@ -733,7 +721,7 @@ class InstanceHelper extends Domain { identityHashCode: remoteObject.objectId.hashCode, kind: InstanceKind.kSet, id: objectId, - classRef: classRef, + classRef: metaData.classRef, ) ..length = length ..elements = elements; @@ -757,7 +745,6 @@ class InstanceHelper extends Domain { /// If [offset] is `null`, assumes 0 offset. /// If [count] is `null`, return all fields starting from the offset. Future _wrappedTypeInstanceFor( - ClassRef classRef, RemoteObject remoteObject, { int? offset, int? count, @@ -790,23 +777,23 @@ class InstanceHelper extends Domain { /// [length] is the expected length of the whole object, read from /// the [ClassMetaData]. Future _plainTypeInstanceFor( - ClassRef classRef, + ClassMetaData metaData, RemoteObject remoteObject, { int? offset, int? count, - int? length, }) async { final objectId = remoteObject.objectId; if (objectId == null) return null; - final fields = await _typeFields(classRef, remoteObject); + final fields = await _typeFields(metaData.classRef, remoteObject); return Instance( identityHashCode: objectId.hashCode, - kind: InstanceKind.kPlainInstance, + kind: InstanceKind.kType, id: objectId, - classRef: classRef, + classRef: metaData.classRef, + name: metaData.typeName, ) - ..length = length + ..length = metaData.length ..offset = offset ..count = count ..fields = fields; @@ -960,6 +947,7 @@ class InstanceHelper extends Domain { id: objectId, identityHashCode: objectId.hashCode, classRef: metaData.classRef, + name: metaData.typeName, )..length = metaData.length; } diff --git a/dwds/lib/src/debugging/metadata/class.dart b/dwds/lib/src/debugging/metadata/class.dart index b85d95b3f..9bfa8722f 100644 --- a/dwds/lib/src/debugging/metadata/class.dart +++ b/dwds/lib/src/debugging/metadata/class.dart @@ -94,6 +94,11 @@ class ClassMetaData { /// example, 'Number', 'JSArray', 'Object'. final String? jsName; + /// Type name for Type instances. + /// + /// For example, 'int', 'String', 'MyClass', 'List'. + final String? typeName; + /// The length of the object, if applicable. final int? length; @@ -110,6 +115,7 @@ class ClassMetaData { factory ClassMetaData({ Object? jsName, + Object? typeName, Object? length, required String runtimeKind, required ClassRef classRef, @@ -119,6 +125,7 @@ class ClassMetaData { id, classRef, jsName, + typeName as String?, int.tryParse('$length'), runtimeKind, ); @@ -128,6 +135,7 @@ class ClassMetaData { this.id, this.classRef, this.jsName, + this.typeName, this.length, this.runtimeKind, ); @@ -146,9 +154,10 @@ class ClassMetaData { return InstanceKind.kRecord; case RuntimeObjectKind.recordType: return InstanceKind.kRecordType; - case RuntimeObjectKind.object: case RuntimeObjectKind.type: case RuntimeObjectKind.wrappedType: + return InstanceKind.kType; + case RuntimeObjectKind.object: case RuntimeObjectKind.nativeError: case RuntimeObjectKind.nativeObject: default: @@ -214,6 +223,8 @@ class ClassMetaDataHelper { result['length'] = arg.types.length; } else if (name == 'Type') { result['runtimeKind'] = '${RuntimeObjectKind.type}'; + var externalType = dart.wrapType(arg); + result['typeName'] = dart.dsendRepl(externalType, "toString", []); } else if (dart.is(arg, dart._Type)) { result['runtimeKind'] = '${RuntimeObjectKind.wrappedType}'; } else if (dart.is(arg, interceptors.NativeError)) { @@ -233,6 +244,7 @@ class ClassMetaDataHelper { ); final metadata = result.value as Map; final jsName = metadata['name']; + final typeName = metadata['typeName']; final dartName = metadata['dartName']; final library = metadata['libraryId']; final runtimeKind = metadata['runtimeKind']; @@ -256,6 +268,7 @@ class ClassMetaDataHelper { return ClassMetaData( jsName: jsName, + typeName: typeName, length: length, runtimeKind: runtimeKind, classRef: classRef, diff --git a/dwds/test/instances/instance_inspection_common.dart b/dwds/test/instances/instance_inspection_common.dart index ad956f8cb..b909e285b 100644 --- a/dwds/test/instances/instance_inspection_common.dart +++ b/dwds/test/instances/instance_inspection_common.dart @@ -178,6 +178,21 @@ class TestInspector { final fields = await Future.wait(fieldRefs.values.map(toStringValue)); return fields.toList(); } + + Future> getElements( + String isolateId, + String instanceId, + ) async { + final instance = await service.getObject(isolateId, instanceId) as Instance; + return Future.wait( + instance.fields!.map( + (e) async => await service.getObject( + isolateId, + (e.value as InstanceRef).id!, + ) as Instance, + ), + ); + } } Map _associationsToMap( @@ -209,8 +224,9 @@ Matcher matchRecordTypeInstanceRef({required int length}) => isA() .having((e) => e.length, 'length', length) .having((e) => e.classRef!, 'classRef', matchRecordTypeClassRef); -Matcher matchTypeInstanceRef = isA() - .having((e) => e.kind, 'kind', InstanceKind.kPlainInstance) +Matcher matchTypeInstanceRef(String name) => isA() + .having((e) => e.kind, 'kind', InstanceKind.kType) + .having((e) => e.name, 'name', name) .having((e) => e.classRef, 'classRef', matchTypeClassRef); Matcher matchPrimitiveInstanceRef({ @@ -228,19 +244,19 @@ Matcher matchPrimitiveInstance({ Matcher matchPlainInstance({required String type}) => isA() .having((e) => e.kind, 'kind', InstanceKind.kPlainInstance) - .having((e) => e.classRef!.name, 'classRef.name', type); + .having((e) => e.classRef, 'classRef', matchClassRef(type)); Matcher matchListInstance({required String type}) => isA() .having((e) => e.kind, 'kind', InstanceKind.kList) - .having((e) => e.classRef!.name, 'classRef.name', type); + .having((e) => e.classRef, 'classRef', matchClassRef(type)); Matcher matchMapInstance({required String type}) => isA() .having((e) => e.kind, 'kind', InstanceKind.kMap) - .having((e) => e.classRef!.name, 'classRef.name', type); + .having((e) => e.classRef, 'classRef', matchClassRef(type)); Matcher matchSetInstance({required String type}) => isA() .having((e) => e.kind, 'kind', InstanceKind.kSet) - .having((e) => e.classRef!.name, 'classRef.name', type); + .having((e) => e.classRef, 'classRef', matchClassRef(type)); Matcher matchRecordInstance({required int length}) => isA() .having((e) => e.kind, 'kind', InstanceKind.kRecord) @@ -255,8 +271,9 @@ Matcher matchRecordTypeInstance({required int length}) => isA() Matcher matchTypeStringInstance(String name) => matchPrimitiveInstance(kind: InstanceKind.kString, value: name); -Matcher matchTypeInstance = isA() - .having((e) => e.kind, 'kind', InstanceKind.kPlainInstance) +Matcher matchTypeInstance(String name) => isA() + .having((e) => e.kind, 'kind', InstanceKind.kType) + .having((e) => e.name, 'name', name) .having((e) => e.classRef, 'classRef', matchTypeClassRef); Matcher matchRecordClass = matchClass(libraryId: 'dart:core', type: 'Record'); diff --git a/dwds/test/instances/record_type_inspection_test.dart b/dwds/test/instances/record_type_inspection_test.dart index 33fdccd6a..d3e6254a0 100644 --- a/dwds/test/instances/record_type_inspection_test.dart +++ b/dwds/test/instances/record_type_inspection_test.dart @@ -60,14 +60,8 @@ Future _runTests({ getDisplayedFields(InstanceRef ref) => testInspector.getDisplayedFields(isolateId, ref); - Future> getElements(String instanceId) async { - final instance = await getObject(instanceId) as Instance; - return Future.wait( - instance.fields!.map( - (e) async => await getObject((e.value as InstanceRef).id!) as Instance, - ), - ); - } + getElements(String instanceId) => + testInspector.getElements(isolateId, instanceId); group('$compilationMode |', () { setUpAll(() async { @@ -109,11 +103,6 @@ Future _runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchRecordTypeClass); - - expect( - await getDisplayedFields(instanceRef), - ['bool', 'int'], - ); }); }); @@ -125,7 +114,11 @@ Future _runTests({ expect( await getElements(instanceId), - [matchTypeInstance, matchTypeInstance], + [matchTypeInstance('bool'), matchTypeInstance('int')], + ); + expect( + await getDisplayedFields(instanceRef), + ['bool', 'int'], ); }); }); @@ -158,11 +151,6 @@ Future _runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchRecordTypeClass); - - expect( - await getDisplayedFields(instanceRef), - ['bool', 'int', 'IdentityMap'], - ); }); }); @@ -174,7 +162,15 @@ Future _runTests({ expect( await getElements(instanceId), - [matchTypeInstance, matchTypeInstance, matchTypeInstance], + [ + matchTypeInstance('bool'), + matchTypeInstance('int'), + matchTypeInstance('IdentityMap'), + ], + ); + expect( + await getDisplayedFields(instanceRef), + ['bool', 'int', 'IdentityMap'], ); }); }); @@ -207,6 +203,23 @@ Future _runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchRecordTypeClass); + }); + }); + + test('complex record type with named fields elements', () async { + await onBreakPoint('printComplexNamedLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instanceId = instanceRef.id!; + + expect( + await getElements(instanceId), + [ + matchTypeInstance('bool'), + matchTypeInstance('int'), + matchTypeInstance('IdentityMap'), + ], + ); expect( await getDisplayedFields(instanceRef), @@ -243,16 +256,6 @@ Future _runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchRecordTypeClass); - - expect( - await getDisplayedFields(instanceRef), - ['bool', '(bool, int)'], - ); - final elements = await getElements(instanceId); - expect( - await getDisplayedFields(elements[1]), - ['bool', 'int'], - ); }); }); @@ -265,11 +268,19 @@ Future _runTests({ final elements = await getElements(instanceId); expect( elements, - [matchTypeInstance, matchRecordTypeInstance(length: 2)], + [matchTypeInstance('bool'), matchRecordTypeInstance(length: 2)], ); expect( await getElements(elements[1].id!), - [matchTypeInstance, matchTypeInstance], + [matchTypeInstance('bool'), matchTypeInstance('int')], + ); + expect( + await getDisplayedFields(instanceRef), + ['bool', '(bool, int)'], + ); + expect( + await getDisplayedFields(elements[1]), + ['bool', 'int'], ); }); }); @@ -292,7 +303,7 @@ Future _runTests({ }); test('nested record type with named fields', () async { - await onBreakPoint('printNestedLocalRecord', (event) async { + await onBreakPoint('printNestedNamedLocalRecord', (event) async { final frame = event.topFrame!.index!; final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); final instanceId = instanceRef.id!; @@ -303,12 +314,28 @@ Future _runTests({ final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchRecordTypeClass); + }); + }); + + test('nested record type with named fields elements', () async { + await onBreakPoint('printNestedNamedLocalRecord', (event) async { + final frame = event.topFrame!.index!; + final instanceRef = await getInstanceRef(frame, 'record.runtimeType'); + final instanceId = instanceRef.id!; + final elements = await getElements(instanceId); + expect( + elements, + [matchTypeInstance('bool'), matchRecordTypeInstance(length: 2)], + ); + expect( + await getElements(elements[1].id!), + [matchTypeInstance('bool'), matchTypeInstance('int')], + ); expect( await getDisplayedFields(instanceRef), ['bool', '(bool, int)'], ); - final elements = await getElements(instanceId); expect( await getDisplayedFields(elements[1]), ['bool', 'int'], diff --git a/dwds/test/instances/type_inspection_test.dart b/dwds/test/instances/type_inspection_test.dart index 7f1c99b79..af3a9b625 100644 --- a/dwds/test/instances/type_inspection_test.dart +++ b/dwds/test/instances/type_inspection_test.dart @@ -54,9 +54,6 @@ Future _runTests({ getObject(instanceId) => service.getObject(isolateId, instanceId); - getDisplayedRef(instanceId) => - testInspector.getDisplayedRef(isolateId, instanceId); - getDisplayedFields(instanceRef) => testInspector.getDisplayedFields(isolateId, instanceRef); @@ -72,9 +69,12 @@ Future _runTests({ depth: depth, ); + getElements(String instanceId) => + testInspector.getElements(isolateId, instanceId); + final matchTypeObject = { 'hashCode': matchPrimitiveInstanceRef(kind: InstanceKind.kDouble), - 'runtimeType': matchTypeInstanceRef, + 'runtimeType': matchTypeInstanceRef('Type'), }; final matchDisplayedTypeObject = [ @@ -115,26 +115,15 @@ Future _runTests({ await onBreakPoint('printSimpleLocalRecord', (event) async { final frame = event.topFrame!.index!; final instanceRef = await getInstanceRef(frame, "'1'.runtimeType"); - final instanceId = instanceRef.id!; + expect(instanceRef, matchTypeInstanceRef('String')); + final instanceId = instanceRef.id!; final instance = await getObject(instanceId); - expect(instance, matchTypeInstance); + expect(instance, matchTypeInstance('String')); final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); expect(await getFields(instanceRef, depth: 1), matchTypeObject); - }); - }); - - test('String type display', () async { - await onBreakPoint('printSimpleLocalRecord', (event) async { - final frame = event.topFrame!.index!; - final instanceRef = await getInstanceRef(frame, "'1'.runtimeType"); - final instanceId = instanceRef.id!; - final displayedRef = await getDisplayedRef(instanceId); - final displayedInstance = await getObject(displayedRef.id); - - expect(displayedInstance, matchTypeStringInstance('String')); expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); }); }); @@ -143,27 +132,15 @@ Future _runTests({ await onBreakPoint('printSimpleLocalRecord', (event) async { final frame = event.topFrame!.index!; final instanceRef = await getInstanceRef(frame, '1.runtimeType'); - final instanceId = instanceRef.id!; + expect(instanceRef, matchTypeInstanceRef('int')); + final instanceId = instanceRef.id!; final instance = await getObject(instanceId); - expect(instance, matchTypeInstance); + expect(instance, matchTypeInstance('int')); final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); expect(await getFields(instanceRef, depth: 1), matchTypeObject); - }); - }); - - test('int type display', () async { - await onBreakPoint('printSimpleLocalRecord', (event) async { - final frame = event.topFrame!.index!; - final instanceRef = await getInstanceRef(frame, '1.runtimeType'); - final instanceId = instanceRef.id!; - - final displayedRef = await getDisplayedRef(instanceId); - final displayedInstance = await getObject(displayedRef.id); - - expect(displayedInstance, matchTypeStringInstance('int')); expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); }); }); @@ -172,27 +149,15 @@ Future _runTests({ await onBreakPoint('printSimpleLocalRecord', (event) async { final frame = event.topFrame!.index!; final instanceRef = await getInstanceRef(frame, '[].runtimeType'); - final instanceId = instanceRef.id!; + expect(instanceRef, matchTypeInstanceRef('List')); + final instanceId = instanceRef.id!; final instance = await getObject(instanceId); - expect(instance, matchTypeInstance); + expect(instance, matchTypeInstance('List')); final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); expect(await getFields(instanceRef, depth: 1), matchTypeObject); - }); - }); - - test('list type display', () async { - await onBreakPoint('printSimpleLocalRecord', (event) async { - final frame = event.topFrame!.index!; - final instanceRef = await getInstanceRef(frame, '[].runtimeType'); - final instanceId = instanceRef.id!; - - final displayedRef = await getDisplayedRef(instanceId); - final displayedInstance = await getObject(displayedRef.id); - - expect(displayedInstance, matchTypeStringInstance('List')); expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); }); }); @@ -202,31 +167,15 @@ Future _runTests({ final frame = event.topFrame!.index!; final instanceRef = await getInstanceRef(frame, '{}.runtimeType'); - final instanceId = instanceRef.id!; + expect(instanceRef, matchTypeInstanceRef('IdentityMap')); + final instanceId = instanceRef.id!; final instance = await getObject(instanceId); - expect(instance, matchTypeInstance); + expect(instance, matchTypeInstance('IdentityMap')); final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); expect(await getFields(instanceRef, depth: 1), matchTypeObject); - }); - }); - - test('map type display', () async { - await onBreakPoint('printSimpleLocalRecord', (event) async { - final frame = event.topFrame!.index!; - final instanceRef = - await getInstanceRef(frame, '{}.runtimeType'); - final instanceId = instanceRef.id!; - - final displayedRef = await getDisplayedRef(instanceId); - final displayedInstance = await getObject(displayedRef.id); - - expect( - displayedInstance, - matchTypeStringInstance('IdentityMap'), - ); expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); }); }); @@ -235,30 +184,15 @@ Future _runTests({ await onBreakPoint('printSimpleLocalRecord', (event) async { final frame = event.topFrame!.index!; final instanceRef = await getInstanceRef(frame, '{}.runtimeType'); - final instanceId = instanceRef.id!; + expect(instanceRef, matchTypeInstanceRef('_IdentityHashSet')); + final instanceId = instanceRef.id!; final instance = await getObject(instanceId); - expect(instance, matchTypeInstance); + expect(instance, matchTypeInstance('_IdentityHashSet')); final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); expect(await getFields(instanceRef, depth: 1), matchTypeObject); - }); - }); - - test('set type display', () async { - await onBreakPoint('printSimpleLocalRecord', (event) async { - final frame = event.topFrame!.index!; - final instanceRef = await getInstanceRef(frame, '{}.runtimeType'); - final instanceId = instanceRef.id!; - - final displayedRef = await getDisplayedRef(instanceId); - final displayedInstance = await getObject(displayedRef.id); - - expect( - displayedInstance, - matchTypeStringInstance('_IdentityHashSet'), - ); expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); }); }); @@ -267,10 +201,15 @@ Future _runTests({ await onBreakPoint('printSimpleLocalRecord', (event) async { final frame = event.topFrame!.index!; final instanceRef = await getInstanceRef(frame, "(0,'a').runtimeType"); - final instanceId = instanceRef.id!; + expect(instanceRef, matchRecordTypeInstanceRef(length: 2)); + final instanceId = instanceRef.id!; final instance = await getObject(instanceId); expect(instance, matchRecordTypeInstance(length: 2)); + expect( + await getElements(instanceId), + [matchTypeInstance('int'), matchTypeInstance('String')], + ); final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchRecordTypeClass); @@ -278,22 +217,6 @@ Future _runTests({ await getFields(instanceRef, depth: 2), {1: matchTypeObject, 2: matchTypeObject}, ); - }); - }); - - test('record type display', () async { - await onBreakPoint('printSimpleLocalRecord', (event) async { - final frame = event.topFrame!.index!; - final instanceRef = await getInstanceRef(frame, "(0,'a').runtimeType"); - final instanceId = instanceRef.id!; - - final displayedRef = await getDisplayedRef(instanceId); - final displayedInstance = await getObject(displayedRef.id); - - expect( - displayedInstance, - matchTypeStringInstance('(int, String)'), - ); expect( await getDisplayedFields(instanceRef), ['int', 'String'], @@ -306,28 +229,15 @@ Future _runTests({ final frame = event.topFrame!.index!; final instanceRef = await getInstanceRef(frame, "Uri.file('').runtimeType"); - final instanceId = instanceRef.id!; + expect(instanceRef, matchTypeInstanceRef('_Uri')); + final instanceId = instanceRef.id!; final instance = await getObject(instanceId); - expect(instance, matchTypeInstance); + expect(instance, matchTypeInstance('_Uri')); final classId = instanceRef.classRef!.id; expect(await getObject(classId), matchTypeClass); expect(await getFields(instanceRef, depth: 1), matchTypeObject); - }); - }); - - test('class type display', () async { - await onBreakPoint('printSimpleLocalRecord', (event) async { - final frame = event.topFrame!.index!; - final instanceRef = - await getInstanceRef(frame, "Uri.file('').runtimeType"); - final instanceId = instanceRef.id!; - - final displayedRef = await getDisplayedRef(instanceId); - final displayedInstance = await getObject(displayedRef.id); - - expect(displayedInstance, matchTypeStringInstance('_Uri')); expect(await getDisplayedFields(instanceRef), matchDisplayedTypeObject); }); }); From 952ce9eb1735a36bd6f9823a09dd9b8fe7beff78 Mon Sep 17 00:00:00 2001 From: Anna Gringauze Date: Thu, 4 May 2023 15:35:53 -0700 Subject: [PATCH 10/13] Addressed CR comments --- dwds/lib/src/debugging/inspector.dart | 2 +- dwds/lib/src/debugging/instance.dart | 132 ++++++++++--------- dwds/lib/src/debugging/metadata/class.dart | 144 +++++++++++---------- dwds/test/instances/instance_test.dart | 56 ++++++-- 4 files changed, 189 insertions(+), 145 deletions(-) diff --git a/dwds/lib/src/debugging/inspector.dart b/dwds/lib/src/debugging/inspector.dart index 6fc6829a8..7d0c75027 100644 --- a/dwds/lib/src/debugging/inspector.dart +++ b/dwds/lib/src/debugging/inspector.dart @@ -628,8 +628,8 @@ class AppInspector implements AppInspectorInterface { Future _subRange( String id, { required int offset, - int? count, required int length, + int? count, }) async { // TODO(#809): Sometimes we already know the type of the object, and // we could take advantage of that to short-circuit. diff --git a/dwds/lib/src/debugging/instance.dart b/dwds/lib/src/debugging/instance.dart index dd2988ec9..ca6162b17 100644 --- a/dwds/lib/src/debugging/instance.dart +++ b/dwds/lib/src/debugging/instance.dart @@ -40,19 +40,21 @@ class InstanceHelper extends Domain { kind: kind, classRef: classRef, id: dartIdFor(remoteObject?.value), - )..valueAsString = '${remoteObject?.value}'; + valueAsString: '${remoteObject?.value}', + ); } /// Creates an [Instance] for a primitive [RemoteObject]. - Instance? _primitiveInstance(String kind, RemoteObject? remote) { - final objectId = remote?.objectId; + Instance? _primitiveInstance(String kind, RemoteObject? remoteObject) { + final objectId = remoteObject?.objectId; if (objectId == null) return null; return Instance( identityHashCode: objectId.hashCode, id: objectId, kind: kind, classRef: classRefFor('dart:core', kind), - )..valueAsString = '${remote?.value}'; + valueAsString: '${remoteObject?.value}', + ); } Instance? _stringInstanceFor( @@ -78,12 +80,12 @@ class InstanceHelper extends Domain { kind: InstanceKind.kString, classRef: classRefForString, id: createId(), - ) - ..valueAsString = preview - ..valueAsStringIsTruncated = truncated - ..length = fullString.length - ..count = (truncated ? preview.length : null) - ..offset = (truncated ? offset : null); + valueAsString: preview, + valueAsStringIsTruncated: truncated, + length: fullString.length, + count: (truncated ? preview.length : null), + offset: (truncated ? offset : null), + ); } Instance? _closureInstanceFor(RemoteObject remoteObject) { @@ -123,50 +125,50 @@ class InstanceHelper extends Domain { switch (metaData.runtimeKind) { case RuntimeObjectKind.function: return _closureInstanceFor(remoteObject); - case RuntimeObjectKind.list: - return await _listInstanceFor( + case RuntimeObjectKind.recordType: + return await _recordTypeInstanceFor( metaData, remoteObject, offset: offset, count: count, ); - case RuntimeObjectKind.set: - return await _setInstanceFor( + case RuntimeObjectKind.type: + return await _plainTypeInstanceFor( metaData, remoteObject, offset: offset, count: count, ); - case RuntimeObjectKind.map: - return await _mapInstanceFor( - metaData, + case RuntimeObjectKind.wrappedType: + return await _wrappedTypeInstanceFor( remoteObject, offset: offset, count: count, ); - case RuntimeObjectKind.record: - return await _recordInstanceFor( + case RuntimeObjectKind.list: + return await _listInstanceFor( metaData, remoteObject, offset: offset, count: count, ); - case RuntimeObjectKind.recordType: - return await _recordTypeInstanceFor( + case RuntimeObjectKind.set: + return await _setInstanceFor( metaData, remoteObject, offset: offset, count: count, ); - case RuntimeObjectKind.type: - return await _plainTypeInstanceFor( + case RuntimeObjectKind.map: + return await _mapInstanceFor( metaData, remoteObject, offset: offset, count: count, ); - case RuntimeObjectKind.wrappedType: - return await _wrappedTypeInstanceFor( + case RuntimeObjectKind.record: + return await _recordInstanceFor( + metaData, remoteObject, offset: offset, count: count, @@ -261,7 +263,8 @@ class InstanceHelper extends Domain { id: objectId, identityHashCode: remoteObject.objectId.hashCode, classRef: metaData.classRef, - )..fields = boundFields; + fields: boundFields, + ); return result; } @@ -353,11 +356,11 @@ class InstanceHelper extends Domain { kind: InstanceKind.kMap, id: objectId, classRef: metaData.classRef, - ) - ..length = metaData.length - ..offset = offset - ..count = rangeCount - ..associations = associations; + length: metaData.length, + offset: offset, + count: rangeCount, + associations: associations, + ); } /// Create a List instance of [classRef] from [remoteObject]. @@ -394,11 +397,11 @@ class InstanceHelper extends Domain { kind: InstanceKind.kList, id: objectId, classRef: metaData.classRef, - ) - ..length = metaData.length - ..elements = elements - ..offset = offset - ..count = rangeCount; + length: metaData.length, + elements: elements, + offset: offset, + count: rangeCount, + ); } /// The elements for a Dart List. @@ -600,11 +603,11 @@ class InstanceHelper extends Domain { kind: InstanceKind.kRecord, id: objectId, classRef: metaData.classRef, - ) - ..length = metaData.length - ..offset = offset - ..count = rangeCount - ..fields = fields; + length: metaData.length, + offset: offset, + count: rangeCount, + fields: fields, + ); } /// Create a RecordType instance with class [classRef] from [remoteObject]. @@ -637,11 +640,11 @@ class InstanceHelper extends Domain { kind: InstanceKind.kRecordType, id: objectId, classRef: metaData.classRef, - ) - ..length = metaData.length - ..offset = offset - ..count = rangeCount - ..fields = fields; + length: metaData.length, + offset: offset, + count: rangeCount, + fields: fields, + ); } /// The field types for a Dart RecordType. @@ -722,9 +725,10 @@ class InstanceHelper extends Domain { kind: InstanceKind.kSet, id: objectId, classRef: metaData.classRef, - ) - ..length = length - ..elements = elements; + length: length, + elements: elements, + ); + if (offset != null && offset > 0) { setInstance.offset = offset; } @@ -792,11 +796,11 @@ class InstanceHelper extends Domain { id: objectId, classRef: metaData.classRef, name: metaData.typeName, - ) - ..length = metaData.length - ..offset = offset - ..count = count - ..fields = fields; + length: metaData.length, + offset: offset, + count: count, + fields: fields, + ); } /// The field types for a Dart RecordType. @@ -948,7 +952,8 @@ class InstanceHelper extends Domain { identityHashCode: objectId.hashCode, classRef: metaData.classRef, name: metaData.typeName, - )..length = metaData.length; + length: metaData.length, + ); } /// Create an [InstanceRef] for the given Chrome [remoteObject]. @@ -969,9 +974,9 @@ class InstanceHelper extends Domain { id: dartIdFor(remoteObject.value), classRef: classRefForString, kind: InstanceKind.kString, - ) - ..valueAsString = stringValue - ..length = stringValue?.length; + valueAsString: stringValue, + length: stringValue?.length, + ); case 'number': return _primitiveInstanceRef(InstanceKind.kDouble, remoteObject); case 'boolean': @@ -993,7 +998,8 @@ class InstanceHelper extends Domain { id: objectId, identityHashCode: objectId.hashCode, classRef: metaData.classRef, - )..length = metaData.length; + length: metaData.length, + ); case 'function': final objectId = remoteObject.objectId; if (objectId == null) { @@ -1008,9 +1014,8 @@ class InstanceHelper extends Domain { id: objectId, identityHashCode: objectId.hashCode, classRef: classRefForClosure, - ) // TODO(grouma) - fill this in properly. - ..closureFunction = FuncRef( + closureFunction: FuncRef( name: functionMetaData.name, id: createId(), // TODO(alanknight): The right ClassRef @@ -1020,8 +1025,9 @@ class InstanceHelper extends Domain { // TODO(annagrin): get information about getters and setters from symbols. // https://github.com/dart-lang/sdk/issues/46723 implicit: false, - ) - ..closureContext = (ContextRef(length: 0, id: createId())); + ), + closureContext: ContextRef(length: 0, id: createId()), + ); default: // Return null for an unsupported type. This is likely a JS construct. return null; diff --git a/dwds/lib/src/debugging/metadata/class.dart b/dwds/lib/src/debugging/metadata/class.dart index 9bfa8722f..656645381 100644 --- a/dwds/lib/src/debugging/metadata/class.dart +++ b/dwds/lib/src/debugging/metadata/class.dart @@ -64,24 +64,49 @@ String classMetaDataIdFor(String library, String? jsName) => '$library:$jsName'; /// Object kinds are determined using DDC runtime API and /// are used to translate from JavaScript objects to their /// vm service protocol representation. -class RuntimeObjectKind { - static const String object = 'object'; - static const String set = 'set'; - static const String list = 'list'; - static const String map = 'map'; - static const String wrappedType = 'wrappedType'; - static const String function = 'function'; - static const String record = 'record'; - static const String type = 'type'; - static const String recordType = 'recordType'; - static const String nativeError = 'nativeError'; - static const String nativeObject = 'nativeObject'; +enum RuntimeObjectKind { + object, + set, + list, + map, + function, + record, + type, + recordType, + wrappedType, + nativeError, + nativeObject; + + static final _valueMap = { + for (var v in values) v.toString(): v, + }; + + static RuntimeObjectKind from(String value) { + final kind = _valueMap[value]; + if (kind == null) { + throw StateError('Unknown runtime object kind: $value'); + } + return kind; + } + + String toInstanceKind() { + return switch (this) { + object || nativeObject || nativeError => InstanceKind.kPlainInstance, + set => InstanceKind.kSet, + list => InstanceKind.kList, + map => InstanceKind.kMap, + function => InstanceKind.kClosure, + record => InstanceKind.kRecord, + type || wrappedType => InstanceKind.kType, + recordType => InstanceKind.kRecordType, + }; + } } /// Meta data for a remote Dart class in Chrome. class ClassMetaData { /// Runtime object kind. - final String runtimeKind; + final RuntimeObjectKind runtimeKind; /// Class id. /// @@ -111,13 +136,13 @@ class ClassMetaData { final ClassRef classRef; /// Instance kind for vm service protocol. - String get kind => _toInstanceKind(runtimeKind); + String get kind => runtimeKind.toInstanceKind(); factory ClassMetaData({ Object? jsName, Object? typeName, Object? length, - required String runtimeKind, + required RuntimeObjectKind runtimeKind, required ClassRef classRef, }) { final id = classMetaDataIdFor(classRef.library!.id!, jsName as String?); @@ -139,40 +164,20 @@ class ClassMetaData { this.length, this.runtimeKind, ); - - static String _toInstanceKind(String runtimeKind) { - switch (runtimeKind) { - case RuntimeObjectKind.function: - return InstanceKind.kClosure; - case RuntimeObjectKind.list: - return InstanceKind.kList; - case RuntimeObjectKind.map: - return InstanceKind.kMap; - case RuntimeObjectKind.set: - return InstanceKind.kSet; - case RuntimeObjectKind.record: - return InstanceKind.kRecord; - case RuntimeObjectKind.recordType: - return InstanceKind.kRecordType; - case RuntimeObjectKind.type: - case RuntimeObjectKind.wrappedType: - return InstanceKind.kType; - case RuntimeObjectKind.object: - case RuntimeObjectKind.nativeError: - case RuntimeObjectKind.nativeObject: - default: - return InstanceKind.kPlainInstance; - } - } } +/// Metadata helper for objects and class refs. +/// +/// Allows to get runtime metadata from DDC runtime +/// and provides functionality to detect some of the +/// runtime kinds of objects. class ClassMetaDataHelper { static final _logger = Logger('ClassMetadata'); final AppInspectorInterface _inspector; /// Runtime object kinds for class refs. - final _runtimeObjectKinds = {}; + final _runtimeObjectKinds = {}; ClassMetaDataHelper(this._inspector); @@ -181,7 +186,7 @@ class ClassMetaDataHelper { /// Returns null if the [remoteObject] is not a Dart class. Future metaDataFor(RemoteObject remoteObject) async { try { - /// TODO(annagrin): this breaks on changes internal + /// TODO(annagrin): this breaks on changes to internal /// type representation in DDC. Replace by runtime API. /// https://github.com/dart-lang/sdk/issues/51583 final evalExpression = ''' @@ -190,14 +195,14 @@ class ClassMetaDataHelper { const dart = sdk.dart; const core = sdk.core; const interceptors = sdk._interceptors; - const classObject = dart.getReifiedType(arg); + const reifiedType = dart.getReifiedType(arg); const result = {}; - var name = classObject.name; + var name = reifiedType.name; result['name'] = name; - result['libraryId'] = dart.getLibraryUri(classObject); - result['dartName'] = dart.typeName(classObject); + result['libraryId'] = dart.getLibraryUri(reifiedType); + result['dartName'] = dart.typeName(reifiedType); result['length'] = arg['length']; result['runtimeKind'] = '${RuntimeObjectKind.object}'; @@ -207,26 +212,28 @@ class ClassMetaDataHelper { result['runtimeKind'] = '${RuntimeObjectKind.list}'; } else if (name == 'LinkedMap' || name == 'IdentityMap') { result['runtimeKind'] = '${RuntimeObjectKind.map}'; - } else if (classObject instanceof dart.AbstractFunctionType) { + } else if (reifiedType instanceof dart.AbstractFunctionType) { result['runtimeKind'] = '${RuntimeObjectKind.function}'; result['name'] = 'Function'; - } else if (classObject instanceof dart.RecordType) { + } else if (reifiedType instanceof dart.RecordType) { result['runtimeKind'] = '${RuntimeObjectKind.record}'; result['name'] = 'Record'; - var shape = classObject.shape; + result['libraryId'] = 'dart:core'; + result['dartName'] = 'Record'; + var shape = reifiedType.shape; var positionalCount = shape.positionals; var namedCount = shape.named == null ? 0 : shape.named.length; result['length'] = positionalCount + namedCount; - } else if (dart.is(arg, dart.RecordType)) { + } else if (arg instanceof dart._Type) { + result['runtimeKind'] = '${RuntimeObjectKind.wrappedType}'; + } else if (arg instanceof dart.RecordType) { result['runtimeKind'] = '${RuntimeObjectKind.recordType}'; result['name'] = 'RecordType'; result['length'] = arg.types.length; - } else if (name == 'Type') { + } else if (dart.is(arg, core.Type)) { result['runtimeKind'] = '${RuntimeObjectKind.type}'; var externalType = dart.wrapType(arg); result['typeName'] = dart.dsendRepl(externalType, "toString", []); - } else if (dart.is(arg, dart._Type)) { - result['runtimeKind'] = '${RuntimeObjectKind.wrappedType}'; } else if (dart.is(arg, interceptors.NativeError)) { result['runtimeKind'] = '${RuntimeObjectKind.nativeError}'; } else if (dart.is(arg, interceptors.JavaScriptObject)) { @@ -247,24 +254,11 @@ class ClassMetaDataHelper { final typeName = metadata['typeName']; final dartName = metadata['dartName']; final library = metadata['libraryId']; - final runtimeKind = metadata['runtimeKind']; + final runtimeKind = RuntimeObjectKind.from(metadata['runtimeKind']); final length = metadata['length']; - // We hide internal types for some instance types. - var classRef = classRefFor(library, dartName); - if (runtimeKind == RuntimeObjectKind.record) { - classRef = classRefForRecord; - } else if (runtimeKind == RuntimeObjectKind.recordType) { - classRef = classRefForRecordType; - } else if (runtimeKind == RuntimeObjectKind.wrappedType) { - classRef = classRefForType; - } - - final id = classRef.id; - if (id == null) { - throw StateError('ClassRef id is null for $dartName'); - } - _runtimeObjectKinds[id] = runtimeKind; + final classRef = classRefFor(library, dartName); + _addRuntimeObjectKind(classRef, runtimeKind); return ClassMetaData( jsName: jsName, @@ -283,6 +277,18 @@ class ClassMetaDataHelper { } } + // Stores runtime object kind for class refs. + void _addRuntimeObjectKind( + ClassRef classRef, + RuntimeObjectKind runtimeKind, + ) { + final id = classRef.id; + if (id == null) { + throw StateError('No classRef id for $classRef'); + } + _runtimeObjectKinds[id] = runtimeKind; + } + /// Returns true for non-dart JavaScript classes. bool isNativeJsObject(ClassRef? classRef) { final id = classRef?.id; diff --git a/dwds/test/instances/instance_test.dart b/dwds/test/instances/instance_test.dart index 8e246b691..2fe80d7be 100644 --- a/dwds/test/instances/instance_test.dart +++ b/dwds/test/instances/instance_test.dart @@ -67,6 +67,7 @@ void main() { final classRef = ref.classRef!; expect(classRef.name, 'Null'); expect(classRef.id, 'classes|dart:core|Null'); + expect(inspector.isDisplayableObject(ref), isTrue); }); test('for a double', () async { @@ -78,6 +79,7 @@ void main() { final classRef = ref.classRef!; expect(classRef.name, 'Double'); expect(classRef.id, 'classes|dart:core|Double'); + expect(inspector.isDisplayableObject(ref), isTrue); }); test('for a class', () async { @@ -91,6 +93,7 @@ void main() { classRef.id, 'classes|org-dartlang-app:///example/scopes/main.dart' '|MyTestClass'); + expect(inspector.isDisplayableObject(ref), isTrue); }); test('for closure', () async { @@ -98,13 +101,14 @@ void main() { final properties = await inspector.getProperties(remoteObject.objectId!); final closure = properties.firstWhere((property) => property.name == 'closure'); - final instanceRef = await inspector.instanceRefFor(closure.value!); - final functionName = instanceRef!.closureFunction!.name; + final ref = await inspector.instanceRefFor(closure.value!); + final functionName = ref!.closureFunction!.name; // Older SDKs do not contain function names if (functionName != 'Closure') { expect(functionName, 'someFunction'); } - expect(instanceRef.kind, InstanceKind.kClosure); + expect(ref.kind, InstanceKind.kClosure); + expect(inspector.isDisplayableObject(ref), isTrue); }); test('for a list', () async { @@ -113,6 +117,7 @@ void main() { expect(ref!.length, greaterThan(0)); expect(ref.kind, InstanceKind.kList); expect(ref.classRef!.name, 'List'); + expect(inspector.isDisplayableObject(ref), isTrue); }); test('for map', () async { @@ -122,6 +127,7 @@ void main() { expect(ref!.length, 2); expect(ref.kind, InstanceKind.kMap); expect(ref.classRef!.name, 'LinkedMap'); + expect(inspector.isDisplayableObject(ref), isTrue); }); test('for an IdentityMap', () async { @@ -131,6 +137,7 @@ void main() { expect(ref!.length, 2); expect(ref.kind, InstanceKind.kMap); expect(ref.classRef!.name, 'IdentityMap'); + expect(inspector.isDisplayableObject(ref), isTrue); }); test('for a native JavaScript error', () async { @@ -139,6 +146,9 @@ void main() { final ref = await inspector.instanceRefFor(remoteObject); expect(ref!.kind, InstanceKind.kPlainInstance); expect(ref.classRef!.name, 'NativeError'); + expect(inspector.isDisplayableObject(ref), isFalse); + expect(inspector.isNativeJsError(ref), isTrue); + expect(inspector.isNativeJsObject(ref), isFalse); }); test('for a native JavaScript type error', () async { @@ -147,6 +157,9 @@ void main() { final ref = await inspector.instanceRefFor(remoteObject); expect(ref!.kind, InstanceKind.kPlainInstance); expect(ref.classRef!.name, 'JSNoSuchMethodError'); + expect(inspector.isDisplayableObject(ref), isFalse); + expect(inspector.isNativeJsError(ref), isTrue); + expect(inspector.isNativeJsObject(ref), isFalse); }); test('for a native JavaScript object', () async { @@ -155,6 +168,9 @@ void main() { final ref = await inspector.instanceRefFor(remoteObject); expect(ref!.kind, InstanceKind.kPlainInstance); expect(ref.classRef!.name, 'LegacyJavaScriptObject'); + expect(inspector.isDisplayableObject(ref), isFalse); + expect(inspector.isNativeJsError(ref), isFalse); + expect(inspector.isNativeJsObject(ref), isTrue); }); }); @@ -186,6 +202,7 @@ void main() { expect(field.name, isNotNull); expect(field.decl!.declaredType, isNotNull); } + expect(inspector.isDisplayableObject(instance), isTrue); }); test('for closure', () async { @@ -196,6 +213,7 @@ void main() { final instance = await inspector.instanceFor(closure.value!); expect(instance!.kind, InstanceKind.kClosure); expect(instance.classRef!.name, 'Closure'); + expect(inspector.isDisplayableObject(instance), isTrue); }); test('for a nested class', () async { @@ -207,6 +225,7 @@ void main() { final classRef = instance.classRef!; expect(classRef, isNotNull); expect(classRef.name, 'MyTestClass'); + expect(inspector.isDisplayableObject(instance), isTrue); }); test('for a list', () async { @@ -218,6 +237,7 @@ void main() { expect(classRef.name, 'List'); final first = instance.elements![0]; expect(first.valueAsString, 'library'); + expect(inspector.isDisplayableObject(instance), isTrue); }); test('for a map', () async { @@ -233,6 +253,7 @@ void main() { final second = instance.associations![1].value as InstanceRef; expect(second.kind, InstanceKind.kString); expect(second.valueAsString, 'something'); + expect(inspector.isDisplayableObject(instance), isTrue); }); test('for an identityMap', () async { @@ -244,6 +265,7 @@ void main() { expect(classRef.name, 'IdentityMap'); final first = instance.associations![0].value; expect(first.valueAsString, '1'); + expect(inspector.isDisplayableObject(instance), isTrue); }); test('for a class that implements List', () async { @@ -257,30 +279,40 @@ void main() { expect(instance.elements, isNull); final field = instance.fields!.first; expect(field.decl!.name, '_internal'); + expect(inspector.isDisplayableObject(instance), isTrue); }); test('for a native JavaScript error', () async { final remoteObject = await inspector.jsEvaluate(interceptorsNewExpression('NativeError')); - final ref = await inspector.instanceFor(remoteObject); - expect(ref!.kind, InstanceKind.kPlainInstance); - expect(ref.classRef!.name, 'NativeError'); + final instance = await inspector.instanceFor(remoteObject); + expect(instance!.kind, InstanceKind.kPlainInstance); + expect(instance.classRef!.name, 'NativeError'); + expect(inspector.isDisplayableObject(instance), isFalse); + expect(inspector.isNativeJsError(instance), isTrue); + expect(inspector.isNativeJsObject(instance), isFalse); }); test('for a native JavaScript type error', () async { final remoteObject = await inspector .jsEvaluate(interceptorsNewExpression('JSNoSuchMethodError')); - final ref = await inspector.instanceFor(remoteObject); - expect(ref!.kind, InstanceKind.kPlainInstance); - expect(ref.classRef!.name, 'JSNoSuchMethodError'); + final instance = await inspector.instanceFor(remoteObject); + expect(instance!.kind, InstanceKind.kPlainInstance); + expect(instance.classRef!.name, 'JSNoSuchMethodError'); + expect(inspector.isDisplayableObject(instance), isFalse); + expect(inspector.isNativeJsError(instance), isTrue); + expect(inspector.isNativeJsObject(instance), isFalse); }); test('for a native JavaScript object', () async { final remoteObject = await inspector .jsEvaluate(interceptorsNewExpression('LegacyJavaScriptObject')); - final ref = await inspector.instanceFor(remoteObject); - expect(ref!.kind, InstanceKind.kPlainInstance); - expect(ref.classRef!.name, 'LegacyJavaScriptObject'); + final instance = await inspector.instanceFor(remoteObject); + expect(instance!.kind, InstanceKind.kPlainInstance); + expect(instance.classRef!.name, 'LegacyJavaScriptObject'); + expect(inspector.isDisplayableObject(instance), isFalse); + expect(inspector.isNativeJsError(instance), isFalse); + expect(inspector.isNativeJsObject(instance), isTrue); }); }); } From e199ba4c87d08d555a0a052f1db83d18ea78646b Mon Sep 17 00:00:00 2001 From: Anna Gringauze Date: Thu, 4 May 2023 15:55:38 -0700 Subject: [PATCH 11/13] Addressed CR comments --- dwds/lib/src/debugging/instance.dart | 20 ++++++++++---------- dwds/lib/src/debugging/metadata/class.dart | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/dwds/lib/src/debugging/instance.dart b/dwds/lib/src/debugging/instance.dart index ca6162b17..f28c39861 100644 --- a/dwds/lib/src/debugging/instance.dart +++ b/dwds/lib/src/debugging/instance.dart @@ -139,8 +139,8 @@ class InstanceHelper extends Domain { offset: offset, count: count, ); - case RuntimeObjectKind.wrappedType: - return await _wrappedTypeInstanceFor( + case RuntimeObjectKind.typeWrapper: + return await _typeWrapperInstanceFor( remoteObject, offset: offset, count: count, @@ -748,7 +748,7 @@ class InstanceHelper extends Domain { /// /// If [offset] is `null`, assumes 0 offset. /// If [count] is `null`, return all fields starting from the offset. - Future _wrappedTypeInstanceFor( + Future _typeWrapperInstanceFor( RemoteObject remoteObject, { int? offset, int? count, @@ -756,8 +756,8 @@ class InstanceHelper extends Domain { final objectId = remoteObject.objectId; if (objectId == null) return null; - final unwrappedType = await _internalType(remoteObject); - return instanceFor(unwrappedType, offset: offset, count: count); + final internalType = await _internalType(remoteObject); + return instanceFor(internalType, offset: offset, count: count); } /// Get inner type from the DDC [type] wrapper instance. @@ -936,14 +936,14 @@ class InstanceHelper extends Domain { /// /// Make sure the instance kind and class ref on the ref matches /// the one on the instance (the internal type instance kind). - Future _wrappedTypeInstanceRef( + Future _typeWrapperInstanceRef( RemoteObject remoteObject, ) async { final objectId = remoteObject.objectId; if (objectId == null) return null; - final unwrappedObject = await _internalType(remoteObject); - final metaData = await metadataHelper.metaDataFor(unwrappedObject); + final internalType = await _internalType(remoteObject); + final metaData = await metadataHelper.metaDataFor(internalType); if (metaData == null) return null; return InstanceRef( @@ -990,8 +990,8 @@ class InstanceHelper extends Domain { } final metaData = await metadataHelper.metaDataFor(remoteObject); if (metaData == null) return null; - if (metaData.runtimeKind == RuntimeObjectKind.wrappedType) { - return _wrappedTypeInstanceRef(remoteObject); + if (metaData.runtimeKind == RuntimeObjectKind.typeWrapper) { + return _typeWrapperInstanceRef(remoteObject); } return InstanceRef( kind: metaData.kind, diff --git a/dwds/lib/src/debugging/metadata/class.dart b/dwds/lib/src/debugging/metadata/class.dart index 656645381..95f52a684 100644 --- a/dwds/lib/src/debugging/metadata/class.dart +++ b/dwds/lib/src/debugging/metadata/class.dart @@ -73,7 +73,7 @@ enum RuntimeObjectKind { record, type, recordType, - wrappedType, + typeWrapper, nativeError, nativeObject; @@ -97,7 +97,7 @@ enum RuntimeObjectKind { map => InstanceKind.kMap, function => InstanceKind.kClosure, record => InstanceKind.kRecord, - type || wrappedType => InstanceKind.kType, + type || typeWrapper => InstanceKind.kType, recordType => InstanceKind.kRecordType, }; } @@ -225,7 +225,7 @@ class ClassMetaDataHelper { var namedCount = shape.named == null ? 0 : shape.named.length; result['length'] = positionalCount + namedCount; } else if (arg instanceof dart._Type) { - result['runtimeKind'] = '${RuntimeObjectKind.wrappedType}'; + result['runtimeKind'] = '${RuntimeObjectKind.typeWrapper}'; } else if (arg instanceof dart.RecordType) { result['runtimeKind'] = '${RuntimeObjectKind.recordType}'; result['name'] = 'RecordType'; From fdfe57ac811c17d07642a7df11d1a0b2a242a052 Mon Sep 17 00:00:00 2001 From: Anna Gringauze Date: Thu, 4 May 2023 17:07:26 -0700 Subject: [PATCH 12/13] Inline type wrapper handling to minimize calls to wrapType --- dwds/lib/src/debugging/instance.dart | 87 ++++------------------ dwds/lib/src/debugging/metadata/class.dart | 40 ++++------ 2 files changed, 28 insertions(+), 99 deletions(-) diff --git a/dwds/lib/src/debugging/instance.dart b/dwds/lib/src/debugging/instance.dart index f28c39861..23c95b2ba 100644 --- a/dwds/lib/src/debugging/instance.dart +++ b/dwds/lib/src/debugging/instance.dart @@ -139,12 +139,6 @@ class InstanceHelper extends Domain { offset: offset, count: count, ); - case RuntimeObjectKind.typeWrapper: - return await _typeWrapperInstanceFor( - remoteObject, - offset: offset, - count: count, - ); case RuntimeObjectKind.list: return await _listInstanceFor( metaData, @@ -627,9 +621,13 @@ class InstanceHelper extends Domain { }) async { final objectId = remoteObject.objectId; if (objectId == null) return null; + // Records are complicated, do an eval to get names and values. - final fields = - await _recordTypeFields(remoteObject, offset: offset, count: count); + final fields = await _recordTypeFields( + remoteObject, + offset: offset, + count: count, + ); final rangeCount = _calculateRangeCount( count: count, elementCount: fields.length, @@ -665,11 +663,12 @@ class InstanceHelper extends Domain { final expression = ''' function() { var sdkUtils = ${globalLoadStrategy.loadModuleSnippet}('dart_sdk').dart; - var shape = sdkUtils.dloadRepl(this, "shape"); + var type = sdkUtils.dloadRepl(this, "_type"); + var shape = sdkUtils.dloadRepl(type, "shape"); var positionalCount = sdkUtils.dloadRepl(shape, "positionals"); var named = sdkUtils.dloadRepl(shape, "named"); named = named == null? null: sdkUtils.dsendRepl(named, "toList", []); - var types = sdkUtils.dloadRepl(this, "types"); + var types = sdkUtils.dloadRepl(type, "types"); types = types.map(t => sdkUtils.wrapType(t)); types = sdkUtils.dsendRepl(types, "toList", []); @@ -739,39 +738,6 @@ class InstanceHelper extends Domain { return setInstance; } - /// Create Type instance with class [classRef] from [remoteObject]. - /// - /// Finds the internal type and returns its object representation. - /// - /// Returns an instance containing [count] fields, if available, - /// starting from the [offset]. - /// - /// If [offset] is `null`, assumes 0 offset. - /// If [count] is `null`, return all fields starting from the offset. - Future _typeWrapperInstanceFor( - RemoteObject remoteObject, { - int? offset, - int? count, - }) async { - final objectId = remoteObject.objectId; - if (objectId == null) return null; - - final internalType = await _internalType(remoteObject); - return instanceFor(internalType, offset: offset, count: count); - } - - /// Get inner type from the DDC [type] wrapper instance. - Future _internalType(RemoteObject type) { - final expression = ''' - function() { - var sdkUtils = ${globalLoadStrategy.loadModuleSnippet}('dart_sdk').dart; - return sdkUtils.dloadRepl(this, "_type"); - } - '''; - - return inspector.jsCallFunctionOn(type, expression, []); - } - /// Create Type instance with class [classRef] from [remoteObject]. /// /// Collect information from the internal [remoteObject] and present @@ -819,9 +785,8 @@ class InstanceHelper extends Domain { final expression = ''' function() { var sdkUtils = ${globalLoadStrategy.loadModuleSnippet}('dart_sdk').dart; - var externalType = sdkUtils.wrapType(this); - var hashCode = sdkUtils.dloadRepl(externalType, "hashCode"); - var runtimeType = sdkUtils.dloadRepl(externalType, "runtimeType"); + var hashCode = sdkUtils.dloadRepl(this, "hashCode"); + var runtimeType = sdkUtils.dloadRepl(this, "runtimeType"); return { hashCode: hashCode, @@ -931,31 +896,6 @@ class InstanceHelper extends Domain { return 'object'; } - /// Create an [InstanceRef] for the given Chrome [remoteObject] - /// for a wrapped type. - /// - /// Make sure the instance kind and class ref on the ref matches - /// the one on the instance (the internal type instance kind). - Future _typeWrapperInstanceRef( - RemoteObject remoteObject, - ) async { - final objectId = remoteObject.objectId; - if (objectId == null) return null; - - final internalType = await _internalType(remoteObject); - final metaData = await metadataHelper.metaDataFor(internalType); - if (metaData == null) return null; - - return InstanceRef( - kind: metaData.kind, - id: objectId, - identityHashCode: objectId.hashCode, - classRef: metaData.classRef, - name: metaData.typeName, - length: metaData.length, - ); - } - /// Create an [InstanceRef] for the given Chrome [remoteObject]. Future _instanceRefForRemote(RemoteObject? remoteObject) async { // If we have a null result, treat it as a reference to null. @@ -990,15 +930,14 @@ class InstanceHelper extends Domain { } final metaData = await metadataHelper.metaDataFor(remoteObject); if (metaData == null) return null; - if (metaData.runtimeKind == RuntimeObjectKind.typeWrapper) { - return _typeWrapperInstanceRef(remoteObject); - } + return InstanceRef( kind: metaData.kind, id: objectId, identityHashCode: objectId.hashCode, classRef: metaData.classRef, length: metaData.length, + name: metaData.typeName, ); case 'function': final objectId = remoteObject.objectId; diff --git a/dwds/lib/src/debugging/metadata/class.dart b/dwds/lib/src/debugging/metadata/class.dart index 95f52a684..9c87aa881 100644 --- a/dwds/lib/src/debugging/metadata/class.dart +++ b/dwds/lib/src/debugging/metadata/class.dart @@ -10,7 +10,6 @@ import 'package:vm_service/vm_service.dart'; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'; const _dartCoreLibrary = 'dart:core'; -const _dartRuntimeLibrary = 'dart:_runtime'; /// A hard-coded ClassRef for the Closure class. final classRefForClosure = classRefFor(_dartCoreLibrary, InstanceKind.kClosure); @@ -18,16 +17,6 @@ final classRefForClosure = classRefFor(_dartCoreLibrary, InstanceKind.kClosure); /// A hard-coded ClassRef for the String class. final classRefForString = classRefFor(_dartCoreLibrary, InstanceKind.kString); -/// A hard-coded ClassRef for the Record class. -final classRefForRecord = classRefFor(_dartCoreLibrary, InstanceKind.kRecord); - -/// A hard-coded ClassRef for the RecordType class. -final classRefForRecordType = - classRefFor(_dartRuntimeLibrary, InstanceKind.kRecordType); - -/// A hard-coded ClassRef for the Type class. -final classRefForType = classRefFor(_dartCoreLibrary, 'Type'); - /// A hard-coded ClassRef for a (non-existent) class called Unknown. final classRefForUnknown = classRefFor(_dartCoreLibrary, 'Unknown'); @@ -73,7 +62,6 @@ enum RuntimeObjectKind { record, type, recordType, - typeWrapper, nativeError, nativeObject; @@ -97,7 +85,7 @@ enum RuntimeObjectKind { map => InstanceKind.kMap, function => InstanceKind.kClosure, record => InstanceKind.kRecord, - type || typeWrapper => InstanceKind.kType, + type => InstanceKind.kType, recordType => InstanceKind.kRecordType, }; } @@ -195,7 +183,13 @@ class ClassMetaDataHelper { const dart = sdk.dart; const core = sdk.core; const interceptors = sdk._interceptors; - const reifiedType = dart.getReifiedType(arg); + var object = arg; + var reifiedType = dart.getReifiedType(arg); + + if (arg instanceof dart._Type) { + object = dart.dloadRepl(arg, "_type"); + reifiedType = dart.getReifiedType(object); + } const result = {}; var name = reifiedType.name; @@ -203,7 +197,7 @@ class ClassMetaDataHelper { result['name'] = name; result['libraryId'] = dart.getLibraryUri(reifiedType); result['dartName'] = dart.typeName(reifiedType); - result['length'] = arg['length']; + result['length'] = object['length']; result['runtimeKind'] = '${RuntimeObjectKind.object}'; if (name == '_HashSet') { @@ -224,19 +218,15 @@ class ClassMetaDataHelper { var positionalCount = shape.positionals; var namedCount = shape.named == null ? 0 : shape.named.length; result['length'] = positionalCount + namedCount; - } else if (arg instanceof dart._Type) { - result['runtimeKind'] = '${RuntimeObjectKind.typeWrapper}'; - } else if (arg instanceof dart.RecordType) { + } else if (object instanceof dart.RecordType) { result['runtimeKind'] = '${RuntimeObjectKind.recordType}'; - result['name'] = 'RecordType'; - result['length'] = arg.types.length; - } else if (dart.is(arg, core.Type)) { + result['length'] = object.types.length; + } else if (dart.is(object, core.Type)) { result['runtimeKind'] = '${RuntimeObjectKind.type}'; - var externalType = dart.wrapType(arg); - result['typeName'] = dart.dsendRepl(externalType, "toString", []); - } else if (dart.is(arg, interceptors.NativeError)) { + result['typeName'] = dart.dsendRepl(arg, "toString", []); + } else if (dart.is(object, interceptors.NativeError)) { result['runtimeKind'] = '${RuntimeObjectKind.nativeError}'; - } else if (dart.is(arg, interceptors.JavaScriptObject)) { + } else if (dart.is(object, interceptors.JavaScriptObject)) { result['runtimeKind'] = '${RuntimeObjectKind.nativeObject}'; } return result; From a467458e73dbbee24e18b51e777e2c11802f0853 Mon Sep 17 00:00:00 2001 From: Anna Gringauze Date: Fri, 5 May 2023 16:19:15 -0700 Subject: [PATCH 13/13] Address CR comments --- dwds/lib/src/debugging/metadata/class.dart | 97 +++++++++---------- .../instances/instance_inspection_common.dart | 76 ++++++++++----- .../instances/instance_inspection_test.dart | 5 +- 3 files changed, 104 insertions(+), 74 deletions(-) diff --git a/dwds/lib/src/debugging/metadata/class.dart b/dwds/lib/src/debugging/metadata/class.dart index 9c87aa881..7cde9989d 100644 --- a/dwds/lib/src/debugging/metadata/class.dart +++ b/dwds/lib/src/debugging/metadata/class.dart @@ -20,13 +20,6 @@ final classRefForString = classRefFor(_dartCoreLibrary, InstanceKind.kString); /// A hard-coded ClassRef for a (non-existent) class called Unknown. final classRefForUnknown = classRefFor(_dartCoreLibrary, 'Unknown'); -/// A hard-coded LibraryRef for a a dart:core library. -final libraryRefForCore = LibraryRef( - id: _dartCoreLibrary, - name: _dartCoreLibrary, - uri: _dartCoreLibrary, -); - /// Returns a [LibraryRef] for the provided library ID and class name. LibraryRef libraryRefFor(String libraryId) => LibraryRef( id: libraryId, @@ -65,17 +58,10 @@ enum RuntimeObjectKind { nativeError, nativeObject; - static final _valueMap = { - for (var v in values) v.toString(): v, - }; - - static RuntimeObjectKind from(String value) { - final kind = _valueMap[value]; - if (kind == null) { - throw StateError('Unknown runtime object kind: $value'); - } - return kind; - } + // TODO(annagrin): Update when built-in parsing is available. + // We can also implement a faster parser if needed. + // https://github.com/dart-lang/language/issues/2348 + static final parse = values.byName; String toInstanceKind() { return switch (this) { @@ -183,51 +169,62 @@ class ClassMetaDataHelper { const dart = sdk.dart; const core = sdk.core; const interceptors = sdk._interceptors; - var object = arg; - var reifiedType = dart.getReifiedType(arg); - - if (arg instanceof dart._Type) { - object = dart.dloadRepl(arg, "_type"); - reifiedType = dart.getReifiedType(object); - } + const reifiedType = dart.getReifiedType(arg); + const name = reifiedType.name; const result = {}; - var name = reifiedType.name; - result['name'] = name; result['libraryId'] = dart.getLibraryUri(reifiedType); result['dartName'] = dart.typeName(reifiedType); - result['length'] = object['length']; - result['runtimeKind'] = '${RuntimeObjectKind.object}'; + result['length'] = arg['length']; + result['runtimeKind'] = '${RuntimeObjectKind.object.name}'; if (name == '_HashSet') { - result['runtimeKind'] = '${RuntimeObjectKind.set}'; - } else if (name == 'JSArray') { - result['runtimeKind'] = '${RuntimeObjectKind.list}'; - } else if (name == 'LinkedMap' || name == 'IdentityMap') { - result['runtimeKind'] = '${RuntimeObjectKind.map}'; - } else if (reifiedType instanceof dart.AbstractFunctionType) { - result['runtimeKind'] = '${RuntimeObjectKind.function}'; + result['runtimeKind'] = '${RuntimeObjectKind.set.name}'; + } + else if (name == 'JSArray') { + result['runtimeKind'] = '${RuntimeObjectKind.list.name}'; + } + else if (name == 'LinkedMap' || name == 'IdentityMap') { + result['runtimeKind'] = '${RuntimeObjectKind.map.name}'; + } + else if (reifiedType instanceof dart.AbstractFunctionType) { + result['runtimeKind'] = '${RuntimeObjectKind.function.name}'; result['name'] = 'Function'; - } else if (reifiedType instanceof dart.RecordType) { - result['runtimeKind'] = '${RuntimeObjectKind.record}'; - result['name'] = 'Record'; + } + else if (reifiedType instanceof dart.RecordType) { + result['runtimeKind'] = '${RuntimeObjectKind.record.name}'; result['libraryId'] = 'dart:core'; + result['name'] = 'Record'; result['dartName'] = 'Record'; var shape = reifiedType.shape; var positionalCount = shape.positionals; var namedCount = shape.named == null ? 0 : shape.named.length; result['length'] = positionalCount + namedCount; - } else if (object instanceof dart.RecordType) { - result['runtimeKind'] = '${RuntimeObjectKind.recordType}'; - result['length'] = object.types.length; - } else if (dart.is(object, core.Type)) { - result['runtimeKind'] = '${RuntimeObjectKind.type}'; - result['typeName'] = dart.dsendRepl(arg, "toString", []); - } else if (dart.is(object, interceptors.NativeError)) { - result['runtimeKind'] = '${RuntimeObjectKind.nativeError}'; - } else if (dart.is(object, interceptors.JavaScriptObject)) { - result['runtimeKind'] = '${RuntimeObjectKind.nativeObject}'; + } + else if (arg instanceof dart._Type) { + var object = dart.dloadRepl(arg, "_type"); + if (object instanceof dart.RecordType) { + result['libraryId'] = 'dart:_runtime'; + result['name'] = 'RecordType'; + result['dartName'] = 'RecordType'; + result['runtimeKind'] = '${RuntimeObjectKind.recordType.name}'; + result['length'] = object.types.length; + } + else if (dart.is(object, core.Type)) { + result['libraryId'] = 'dart:core'; + result['name'] = 'Type'; + result['dartName'] = 'Type'; + result['runtimeKind'] = '${RuntimeObjectKind.type.name}'; + result['typeName'] = dart.dsendRepl(arg, "toString", []); + result['length'] = object['length']; + } + } + else if (dart.is(arg, interceptors.NativeError)) { + result['runtimeKind'] = '${RuntimeObjectKind.nativeError.name}'; + } + else if (dart.is(arg, interceptors.JavaScriptObject)) { + result['runtimeKind'] = '${RuntimeObjectKind.nativeObject.name}'; } return result; } @@ -244,7 +241,7 @@ class ClassMetaDataHelper { final typeName = metadata['typeName']; final dartName = metadata['dartName']; final library = metadata['libraryId']; - final runtimeKind = RuntimeObjectKind.from(metadata['runtimeKind']); + final runtimeKind = RuntimeObjectKind.parse(metadata['runtimeKind']); final length = metadata['length']; final classRef = classRefFor(library, dartName); diff --git a/dwds/test/instances/instance_inspection_common.dart b/dwds/test/instances/instance_inspection_common.dart index b909e285b..64a6d03ee 100644 --- a/dwds/test/instances/instance_inspection_common.dart +++ b/dwds/test/instances/instance_inspection_common.dart @@ -5,7 +5,6 @@ @TestOn('vm') @Timeout(Duration(minutes: 2)) -import 'package:dwds/src/debugging/metadata/class.dart'; import 'package:test/test.dart'; import 'package:vm_service/vm_service.dart'; @@ -242,31 +241,36 @@ Matcher matchPrimitiveInstance({ .having((e) => e.kind, 'kind', kind) .having(_getValue, 'value', value); -Matcher matchPlainInstance({required String type}) => isA() - .having((e) => e.kind, 'kind', InstanceKind.kPlainInstance) - .having((e) => e.classRef, 'classRef', matchClassRef(type)); +Matcher matchPlainInstance({required libraryId, required String type}) => + isA() + .having((e) => e.kind, 'kind', InstanceKind.kPlainInstance) + .having( + (e) => e.classRef, + 'classRef', + matchClassRef(name: type, libraryId: libraryId), + ); Matcher matchListInstance({required String type}) => isA() .having((e) => e.kind, 'kind', InstanceKind.kList) - .having((e) => e.classRef, 'classRef', matchClassRef(type)); + .having((e) => e.classRef, 'classRef', matchListClassRef(type)); Matcher matchMapInstance({required String type}) => isA() .having((e) => e.kind, 'kind', InstanceKind.kMap) - .having((e) => e.classRef, 'classRef', matchClassRef(type)); + .having((e) => e.classRef, 'classRef', matchMapClassRef(type)); Matcher matchSetInstance({required String type}) => isA() .having((e) => e.kind, 'kind', InstanceKind.kSet) - .having((e) => e.classRef, 'classRef', matchClassRef(type)); + .having((e) => e.classRef, 'classRef', matchSetClassRef(type)); Matcher matchRecordInstance({required int length}) => isA() .having((e) => e.kind, 'kind', InstanceKind.kRecord) .having((e) => e.length, 'length', length) - .having((e) => e.classRef!, 'classRef', matchRecordClassRef); + .having((e) => e.classRef, 'classRef', matchRecordClassRef); Matcher matchRecordTypeInstance({required int length}) => isA() .having((e) => e.kind, 'kind', InstanceKind.kRecordType) .having((e) => e.length, 'length', length) - .having((e) => e.classRef!, 'classRef', matchRecordTypeClassRef); + .having((e) => e.classRef, 'classRef', matchRecordTypeClassRef); Matcher matchTypeStringInstance(String name) => matchPrimitiveInstance(kind: InstanceKind.kString, value: name); @@ -276,22 +280,38 @@ Matcher matchTypeInstance(String name) => isA() .having((e) => e.name, 'name', name) .having((e) => e.classRef, 'classRef', matchTypeClassRef); -Matcher matchRecordClass = matchClass(libraryId: 'dart:core', type: 'Record'); +Matcher matchRecordClass = + matchClass(name: _recordClass, libraryId: _dartCoreLibrary); Matcher matchRecordTypeClass = - matchClass(libraryId: 'dart:_runtime', type: 'RecordType'); -Matcher matchTypeClass = matchClass(libraryId: 'dart:core', type: 'Type'); - -Matcher matchClass({required String libraryId, required String type}) => - isA() - .having((e) => e.name, 'name', type) - .having((e) => e.id, 'id', classIdFor(libraryId, type)); + matchClass(name: _recordTypeClass, libraryId: _dartRuntimeLibrary); +Matcher matchTypeClass = + matchClass(name: _typeClass, libraryId: _dartCoreLibrary); -Matcher matchRecordClassRef = matchClassRef('Record'); -Matcher matchRecordTypeClassRef = matchClassRef('RecordType'); -Matcher matchTypeClassRef = matchClassRef('Type'); - -Matcher matchClassRef(String type) => - isA().having((e) => e.name, 'class name', type); +Matcher matchClass({String? name, String? libraryId}) => isA() + .having((e) => e.name, 'name', name) + .having((e) => e.library, 'library', matchLibraryRef(libraryId)); + +Matcher matchRecordClassRef = + matchClassRef(name: _recordClass, libraryId: _dartCoreLibrary); +Matcher matchRecordTypeClassRef = + matchClassRef(name: _recordTypeClass, libraryId: _dartRuntimeLibrary); +Matcher matchTypeClassRef = + matchClassRef(name: _typeClass, libraryId: _dartCoreLibrary); +Matcher matchListClassRef(String type) => + matchClassRef(name: type, libraryId: _dartInterceptorsLibrary); +Matcher matchMapClassRef(String type) => + matchClassRef(name: type, libraryId: _dartJsHelperLibrary); +Matcher matchSetClassRef(String type) => + matchClassRef(name: type, libraryId: _dartCollectionLibrary); + +Matcher matchClassRef({String? name, String? libraryId}) => isA() + .having((e) => e.name, 'class name', name) + .having((e) => e.library, 'library', matchLibraryRef(libraryId)); + +Matcher matchLibraryRef(String? libraryId) => isA() + .having((e) => e.name, 'name', libraryId) + .having((e) => e.id, 'id', libraryId) + .having((e) => e.uri, 'uri', libraryId); Object? _getValue(InstanceRef instanceRef) { switch (instanceRef.kind) { @@ -306,3 +326,13 @@ Object? _getValue(InstanceRef instanceRef) { return null; } } + +final _dartCoreLibrary = 'dart:core'; +final _dartRuntimeLibrary = 'dart:_runtime'; +final _dartInterceptorsLibrary = 'dart:_interceptors'; +final _dartJsHelperLibrary = 'dart:_js_helper'; +final _dartCollectionLibrary = 'dart:collection'; + +final _recordClass = 'Record'; +final _recordTypeClass = 'RecordType'; +final _typeClass = 'Type'; diff --git a/dwds/test/instances/instance_inspection_test.dart b/dwds/test/instances/instance_inspection_test.dart index d627a6bcc..9be7dc107 100644 --- a/dwds/test/instances/instance_inspection_test.dart +++ b/dwds/test/instances/instance_inspection_test.dart @@ -106,7 +106,10 @@ Future _runTests({ final instanceId = instanceRef.id!; expect( await getObject(instanceId), - matchPlainInstance(type: 'MainClass'), + matchPlainInstance( + libraryId: 'org-dartlang-app:///web/main.dart', + type: 'MainClass', + ), ); expect(await getFields(instanceRef), {'_field': 1, 'field': 2});