blob: 17bcbbc45a83ecf961be0d5bcfc99746e0ffce25 [file] [log] [blame]
// Copyright (c) 2020, 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.
import 'package:dwds/src/config/tool_configuration.dart';
import 'package:dwds/src/services/chrome_debug_exception.dart';
import 'package:dwds/src/utilities/domain.dart';
import 'package:logging/logging.dart';
import 'package:vm_service/vm_service.dart';
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
const _dartCoreLibrary = 'dart:core';
/// A hard-coded ClassRef for the Closure class.
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 a (non-existent) class called Unknown.
final classRefForUnknown = classRefFor(_dartCoreLibrary, 'Unknown');
/// Returns a [LibraryRef] for the provided library ID and class name.
LibraryRef libraryRefFor(String libraryId) => LibraryRef(
id: libraryId,
name: libraryId,
uri: libraryId,
);
/// Returns a [ClassRef] for the provided library ID and class name.
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';
String classMetaDataIdFor(ClassRef classRef) =>
'${classRef.library!.id!}:${classRef.name}';
/// 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.
enum RuntimeObjectKind {
object,
set,
list,
map,
function,
record,
type,
recordType,
nativeError,
nativeObject;
// 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) {
object || nativeObject || nativeError => InstanceKind.kPlainInstance,
set => InstanceKind.kSet,
list => InstanceKind.kList,
map => InstanceKind.kMap,
function => InstanceKind.kClosure,
record => InstanceKind.kRecord,
type => InstanceKind.kType,
recordType => InstanceKind.kRecordType,
};
}
}
/// Meta data for a remote Dart class in Chrome.
class ClassMetaData {
/// Runtime object kind.
final RuntimeObjectKind runtimeKind;
/// Class id.
///
/// Takes the form of 'libraryId:name'.
final String id;
/// Type name for Type instances.
///
/// For example, 'int', 'String', 'MyClass', 'List<int>'.
final String? typeName;
/// The length of the object, if applicable.
final int? length;
/// The dart type name for the object.
///
/// For example, 'int', 'List<String>', 'Null'
String? get dartName => classRef.name;
/// Class ref for the class metadata.
final ClassRef classRef;
/// Instance kind for vm service protocol.
String get kind => runtimeKind.toInstanceKind();
factory ClassMetaData({
Object? typeName,
Object? length,
required RuntimeObjectKind runtimeKind,
required ClassRef classRef,
}) {
final id = classMetaDataIdFor(classRef);
return ClassMetaData._(
id,
classRef,
typeName as String?,
int.tryParse('$length'),
runtimeKind,
);
}
ClassMetaData._(
this.id,
this.classRef,
this.typeName,
this.length,
this.runtimeKind,
);
}
/// 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 = <String, RuntimeObjectKind>{};
ClassMetaDataHelper(this._inspector);
/// Returns the [ClassMetaData] for the Chrome [remoteObject].
///
/// Returns null if the [remoteObject] is not a Dart class.
Future<ClassMetaData?> metaDataFor(RemoteObject remoteObject) async {
try {
final evalExpression = '''
function(arg) {
const sdk = ${globalToolConfiguration.loadStrategy.loadModuleSnippet}('dart_sdk');
const dart = sdk.dart;
return dart.getObjectMetadata(arg);
}
''';
final result = await _inspector.jsCallFunctionOn(
remoteObject,
evalExpression,
[remoteObject],
returnByValue: true,
);
final metadata = result.value as Map;
final className = metadata['className'];
if (className == null) {
return null;
}
final typeName = metadata['typeName'];
final library = metadata['libraryId'];
final runtimeKind = RuntimeObjectKind.parse(metadata['runtimeKind']);
final length = metadata['length'];
final classRef = classRefFor(library, className);
_addRuntimeObjectKind(classRef, runtimeKind);
return ClassMetaData(
typeName: typeName,
length: length,
runtimeKind: runtimeKind,
classRef: classRef,
);
} on ChromeDebugException catch (e, s) {
_logger.fine(
'Could not create class metadata for ${remoteObject.json}',
e,
s,
);
return null;
}
}
// 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;
return id != null &&
_runtimeObjectKinds[id] == RuntimeObjectKind.nativeObject;
}
/// Returns true for non-dart JavaScript classes.
bool isNativeJsError(ClassRef? classRef) {
final id = classRef?.id;
return id != null &&
_runtimeObjectKinds[id] == RuntimeObjectKind.nativeError;
}
}