[vm, service, observatory] Add ability to retrieve the set of all instances of a class, implementation hierarchy or interface hierarchy as an array.

Allows developers to perform arbitrary filtering or analysis of instances, such as finding the largest strings, degreeses of duplication, histograms of various properties, etc.

TEST=ci
Bug: https://github.com/dart-lang/sdk/issues/44479
Change-Id: I0b4005b5778038945e5f1b2d7858806c8e0dbbff
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/176381
Commit-Queue: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
This commit is contained in:
Ryan Macnak
2021-04-01 20:53:22 +00:00
committed by commit-bot@chromium.org
parent 22e9b612a1
commit 170bda2b74
15 changed files with 620 additions and 27 deletions
@@ -6,6 +6,7 @@ import 'dart:html';
import 'dart:async';
import 'package:observatory/models.dart' as M;
import 'package:observatory/src/elements/class_ref.dart';
import 'package:observatory/src/elements/helpers/any_ref.dart';
import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
import 'package:observatory/src/elements/helpers/custom_element.dart';
import 'package:observatory/src/elements/inbound_references.dart';
@@ -25,6 +26,12 @@ class ClassInstancesElement extends CustomElement implements Renderable {
late M.ReachableSizeRepository _reachableSizes;
late M.StronglyReachableInstancesRepository _stronglyReachableInstances;
late M.ObjectRepository _objects;
M.Guarded<M.InstanceRef>? _allInstances = null;
bool _loadingAllInstances = false;
M.Guarded<M.InstanceRef>? _allSubclassInstances = null;
bool _loadingAllSubclassInstances = false;
M.Guarded<M.InstanceRef>? _allImplementorInstances = null;
bool _loadingAllImplementorInstances = false;
M.Guarded<M.Instance>? _retainedSize = null;
bool _loadingRetainedBytes = false;
M.Guarded<M.Instance>? _reachableSize = null;
@@ -106,6 +113,41 @@ class ClassInstancesElement extends CustomElement implements Renderable {
..classes = ['memberValue']
..children = <Element>[_strong!.element]
],
new DivElement()
..classes = ['memberItem']
..children = <Element>[
new DivElement()
..classes = ['memberName']
..text = 'all direct instances'
..title = 'All instances whose class is exactly this class',
new DivElement()
..classes = ['memberValue']
..children = _createAllInstances()
],
new DivElement()
..classes = ['memberItem']
..children = <Element>[
new DivElement()
..classes = ['memberName']
..text = 'all instances of subclasses'
..title =
'All instances whose class is a subclass of this class',
new DivElement()
..classes = ['memberValue']
..children = _createAllSubclassInstances()
],
new DivElement()
..classes = ['memberItem']
..children = <Element>[
new DivElement()
..classes = ['memberName']
..text = 'all instances of implementors'
..title =
'All instances whose class implements the implicit interface of this class',
new DivElement()
..classes = ['memberValue']
..children = _createAllImplementorInstances()
],
new DivElement()
..classes = ['memberItem']
..title = 'Space reachable from this object, '
@@ -134,6 +176,96 @@ class ClassInstancesElement extends CustomElement implements Renderable {
];
}
List<Element> _createAllInstances() {
final content = <Element>[];
if (_allInstances != null) {
if (_allInstances!.isSentinel) {
content.add(new SentinelValueElement(_allInstances!.asSentinel!,
queue: _r.queue)
.element);
} else {
content.add(anyRef(_isolate, _allInstances!.asValue!, _objects));
}
} else {
content.add(new SpanElement()..text = '...');
}
final button = new ButtonElement()
..classes = ['reachable_size']
..disabled = _loadingAllInstances
..text = '';
button.onClick.listen((_) async {
button.disabled = true;
_loadingAllInstances = true;
_allInstances =
await _stronglyReachableInstances.getAsArray(_isolate, _cls);
_loadingAllInstances = false;
_r.dirty();
});
content.add(button);
return content;
}
List<Element> _createAllSubclassInstances() {
final content = <Element>[];
if (_allSubclassInstances != null) {
if (_allSubclassInstances!.isSentinel) {
content.add(new SentinelValueElement(_allSubclassInstances!.asSentinel!,
queue: _r.queue)
.element);
} else {
content
.add(anyRef(_isolate, _allSubclassInstances!.asValue!, _objects));
}
} else {
content.add(new SpanElement()..text = '...');
}
final button = new ButtonElement()
..classes = ['reachable_size']
..disabled = _loadingAllSubclassInstances
..text = '';
button.onClick.listen((_) async {
button.disabled = true;
_loadingAllSubclassInstances = true;
_allSubclassInstances = await _stronglyReachableInstances
.getAsArray(_isolate, _cls, includeSubclasses: true);
_loadingAllSubclassInstances = false;
_r.dirty();
});
content.add(button);
return content;
}
List<Element> _createAllImplementorInstances() {
final content = <Element>[];
if (_allImplementorInstances != null) {
if (_allImplementorInstances!.isSentinel) {
content.add(new SentinelValueElement(
_allImplementorInstances!.asSentinel!,
queue: _r.queue)
.element);
} else {
content.add(
anyRef(_isolate, _allImplementorInstances!.asValue!, _objects));
}
} else {
content.add(new SpanElement()..text = '...');
}
final button = new ButtonElement()
..classes = ['reachable_size']
..disabled = _loadingAllImplementorInstances
..text = '';
button.onClick.listen((_) async {
button.disabled = true;
_loadingAllImplementorInstances = true;
_allImplementorInstances = await _stronglyReachableInstances
.getAsArray(_isolate, _cls, includeImplementors: true);
_loadingAllImplementorInstances = false;
_r.dirty();
});
content.add(button);
return content;
}
List<Element> _createReachableSizeValue() {
final content = <Element>[];
if (_reachableSize != null) {
@@ -157,6 +289,7 @@ class ClassInstancesElement extends CustomElement implements Renderable {
button.disabled = true;
_loadingReachableBytes = true;
_reachableSize = await _reachableSizes.get(_isolate, _cls.id!);
_loadingReachableBytes = false;
_r.dirty();
});
content.add(button);
@@ -186,6 +319,7 @@ class ClassInstancesElement extends CustomElement implements Renderable {
button.disabled = true;
_loadingRetainedBytes = true;
_retainedSize = await _retainedSizes.get(_isolate, _cls.id!);
_loadingRetainedBytes = false;
_r.dirty();
});
content.add(button);
@@ -6,4 +6,6 @@ part of models;
abstract class StronglyReachableInstancesRepository {
Future<InstanceSet> get(IsolateRef isolate, ClassRef cls, {int limit: 100});
Future<Guarded<InstanceRef>> getAsArray(IsolateRef isolate, ClassRef cls,
{bool includeSubclasses: false, includeImplementors: false});
}
@@ -15,4 +15,18 @@ class StronglyReachableInstancesRepository
assert(limit != null);
return (await isolate.getInstances(cls, limit)) as S.InstanceSet;
}
Future<M.Guarded<M.InstanceRef>> getAsArray(M.IsolateRef i, M.ClassRef c,
{bool includeSubclasses: false, includeImplementors: false}) async {
S.Isolate isolate = i as S.Isolate;
S.Class cls = c as S.Class;
assert(isolate != null);
assert(cls != null);
final response = await isolate.invokeRpc('_getInstancesAsArray', {
'objectId': cls.id,
'includeSubclasses': includeSubclasses,
'includeImplementors': includeImplementors
});
return new S.Guarded<S.Instance>(response);
}
}
@@ -0,0 +1,75 @@
// 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:observatory/service_io.dart";
import "package:test/test.dart";
import "test_helper.dart";
@pragma("vm:entry-point")
class Class {}
@pragma("vm:entry-point")
class Subclass extends Class {}
@pragma("vm:entry-point")
class Implementor implements Class {}
@pragma("vm:entry-point")
var aClass;
@pragma("vm:entry-point")
var aSubclass;
@pragma("vm:entry-point")
var anImplementor;
@pragma("vm:entry-point")
allocate() {
aClass = new Class();
aSubclass = new Subclass();
anImplementor = new Implementor();
}
var tests = <IsolateTest>[
(Isolate isolate) async {
invoke(String selector) async {
Map params = {
"targetId": isolate.rootLibrary.id,
"selector": selector,
"argumentIds": <String>[],
};
return await isolate.invokeRpcNoUpgrade("invoke", params);
}
Future<int> instanceCount(String className,
{bool includeSubclasses: false,
bool includeImplementors: false}) async {
Map params = {
"objectId": isolate.rootLibrary.classes
.singleWhere((cls) => cls.name == className)
.id,
"includeSubclasses": includeSubclasses,
"includeImplementors": includeImplementors,
};
var result =
await isolate.invokeRpcNoUpgrade("_getInstancesAsArray", params);
expect(result["type"], equals("@Instance"));
expect(result["kind"], equals("List"));
return result["length"] as int;
}
await isolate.rootLibrary.load();
expect(await instanceCount("Class"), equals(0));
expect(await instanceCount("Class", includeSubclasses: true), equals(0));
expect(await instanceCount("Class", includeImplementors: true), equals(0));
await invoke("allocate");
expect(await instanceCount("Class"), equals(1));
expect(await instanceCount("Class", includeSubclasses: true), equals(2));
expect(await instanceCount("Class", includeImplementors: true), equals(3));
},
];
main(args) async => runIsolateTests(args, tests);
@@ -6,6 +6,7 @@ import 'dart:html';
import 'dart:async';
import 'package:observatory_2/models.dart' as M;
import 'package:observatory_2/src/elements/class_ref.dart';
import 'package:observatory_2/src/elements/helpers/any_ref.dart';
import 'package:observatory_2/src/elements/helpers/rendering_scheduler.dart';
import 'package:observatory_2/src/elements/helpers/custom_element.dart';
import 'package:observatory_2/src/elements/inbound_references.dart';
@@ -25,6 +26,12 @@ class ClassInstancesElement extends CustomElement implements Renderable {
M.ReachableSizeRepository _reachableSizes;
M.StronglyReachableInstancesRepository _stronglyReachableInstances;
M.ObjectRepository _objects;
M.Guarded<M.InstanceRef> _allInstances = null;
bool _loadingAllInstances = false;
M.Guarded<M.InstanceRef> _allSubclassInstances = null;
bool _loadingAllSubclassInstances = false;
M.Guarded<M.InstanceRef> _allImplementorInstances = null;
bool _loadingAllImplementorInstances = false;
M.Guarded<M.Instance> _retainedSize = null;
bool _loadingRetainedBytes = false;
M.Guarded<M.Instance> _reachableSize = null;
@@ -106,6 +113,41 @@ class ClassInstancesElement extends CustomElement implements Renderable {
..classes = ['memberValue']
..children = <Element>[_strong.element]
],
new DivElement()
..classes = ['memberItem']
..children = <Element>[
new DivElement()
..classes = ['memberName']
..text = 'all direct instances'
..title = 'All instances whose class is exactly this class',
new DivElement()
..classes = ['memberValue']
..children = _createAllInstances()
],
new DivElement()
..classes = ['memberItem']
..children = <Element>[
new DivElement()
..classes = ['memberName']
..text = 'all instances of subclasses'
..title =
'All instances whose class is a subclass of this class',
new DivElement()
..classes = ['memberValue']
..children = _createAllSubclassInstances()
],
new DivElement()
..classes = ['memberItem']
..children = <Element>[
new DivElement()
..classes = ['memberName']
..text = 'all instances of implementors'
..title =
'All instances whose class implements the implicit interface of this class',
new DivElement()
..classes = ['memberValue']
..children = _createAllImplementorInstances()
],
new DivElement()
..classes = ['memberItem']
..title = 'Space reachable from this object, '
@@ -134,6 +176,95 @@ class ClassInstancesElement extends CustomElement implements Renderable {
];
}
List<Element> _createAllInstances() {
final content = <Element>[];
if (_allInstances != null) {
if (_allInstances.isSentinel) {
content.add(
new SentinelValueElement(_allInstances.asSentinel, queue: _r.queue)
.element);
} else {
content.add(anyRef(_isolate, _allInstances.asValue, _objects));
}
} else {
content.add(new SpanElement()..text = '...');
}
final button = new ButtonElement()
..classes = ['reachable_size']
..disabled = _loadingAllInstances
..text = '';
button.onClick.listen((_) async {
button.disabled = true;
_loadingAllInstances = true;
_allInstances =
await _stronglyReachableInstances.getAsArray(_isolate, _cls);
_loadingAllInstances = false;
_r.dirty();
});
content.add(button);
return content;
}
List<Element> _createAllSubclassInstances() {
final content = <Element>[];
if (_allSubclassInstances != null) {
if (_allSubclassInstances.isSentinel) {
content.add(new SentinelValueElement(_allSubclassInstances.asSentinel,
queue: _r.queue)
.element);
} else {
content.add(anyRef(_isolate, _allSubclassInstances.asValue, _objects));
}
} else {
content.add(new SpanElement()..text = '...');
}
final button = new ButtonElement()
..classes = ['reachable_size']
..disabled = _loadingAllSubclassInstances
..text = '';
button.onClick.listen((_) async {
button.disabled = true;
_loadingAllSubclassInstances = true;
_allSubclassInstances = await _stronglyReachableInstances
.getAsArray(_isolate, _cls, includeSubclasses: true);
_loadingAllSubclassInstances = false;
_r.dirty();
});
content.add(button);
return content;
}
List<Element> _createAllImplementorInstances() {
final content = <Element>[];
if (_allImplementorInstances != null) {
if (_allImplementorInstances.isSentinel) {
content.add(new SentinelValueElement(
_allImplementorInstances.asSentinel,
queue: _r.queue)
.element);
} else {
content
.add(anyRef(_isolate, _allImplementorInstances.asValue, _objects));
}
} else {
content.add(new SpanElement()..text = '...');
}
final button = new ButtonElement()
..classes = ['reachable_size']
..disabled = _loadingAllImplementorInstances
..text = '';
button.onClick.listen((_) async {
button.disabled = true;
_loadingAllImplementorInstances = true;
_allImplementorInstances = await _stronglyReachableInstances
.getAsArray(_isolate, _cls, includeImplementors: true);
_loadingAllImplementorInstances = false;
_r.dirty();
});
content.add(button);
return content;
}
List<Element> _createReachableSizeValue() {
final content = <Element>[];
if (_reachableSize != null) {
@@ -6,4 +6,6 @@ part of models;
abstract class StronglyReachableInstancesRepository {
Future<InstanceSet> get(IsolateRef isolate, ClassRef cls, {int limit: 100});
Future<Guarded<InstanceRef>> getAsArray(IsolateRef isolate, ClassRef cls,
{bool includeSubclasses: false, includeImplementors: false});
}
@@ -15,4 +15,18 @@ class StronglyReachableInstancesRepository
assert(limit != null);
return (await isolate.getInstances(cls, limit)) as S.InstanceSet;
}
Future<M.Guarded<M.InstanceRef>> getAsArray(M.IsolateRef i, M.ClassRef c,
{bool includeSubclasses: false, includeImplementors: false}) async {
S.Isolate isolate = i as S.Isolate;
S.Class cls = c as S.Class;
assert(isolate != null);
assert(cls != null);
final response = await isolate.invokeRpc('_getInstancesAsArray', {
'objectId': cls.id,
'includeSubclasses': includeSubclasses == true,
'includeImplementors': includeImplementors == true,
});
return new S.Guarded<S.Instance>(response);
}
}
@@ -0,0 +1,72 @@
// 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:observatory_2/service_io.dart";
import "package:test/test.dart";
import "test_helper.dart";
class Class {}
class Subclass extends Class {}
class Implementor implements Class {}
@pragma("vm:entry-point")
var aClass;
@pragma("vm:entry-point")
var aSubclass;
@pragma("vm:entry-point")
var anImplementor;
@pragma("vm:entry-point")
allocate() {
aClass = new Class();
aSubclass = new Subclass();
anImplementor = new Implementor();
}
var tests = <IsolateTest>[
(Isolate isolate) async {
invoke(String selector) async {
Map params = {
"targetId": isolate.rootLibrary.id,
"selector": selector,
"argumentIds": <String>[],
};
return await isolate.invokeRpcNoUpgrade("invoke", params);
}
Future<int> instanceCount(String className,
{bool includeSubclasses: false,
bool includeImplementors: false}) async {
Map params = {
"objectId": isolate.rootLibrary.classes
.singleWhere((cls) => cls.name == className)
.id,
"includeSubclasses": includeSubclasses,
"includeImplementors": includeImplementors,
};
var result =
await isolate.invokeRpcNoUpgrade("_getInstancesAsArray", params);
expect(result["type"], equals("@Instance"));
expect(result["kind"], equals("List"));
return result["length"] as int;
}
await isolate.rootLibrary.load();
expect(await instanceCount("Class"), equals(0));
expect(await instanceCount("Class", includeSubclasses: true), equals(0));
expect(await instanceCount("Class", includeImplementors: true), equals(0));
await invoke("allocate");
expect(await instanceCount("Class"), equals(1));
expect(await instanceCount("Class", includeSubclasses: true), equals(2));
expect(await instanceCount("Class", includeImplementors: true), equals(3));
},
];
main(args) async => runIsolateTests(args, tests);
+14
View File
@@ -125,6 +125,20 @@ class SharedClassTable {
trace_allocation_table_.load()[cid] = trace ? 1 : 0;
}
bool TraceAllocationFor(intptr_t cid);
void SetCollectInstancesFor(intptr_t cid, bool trace) {
ASSERT(cid > 0);
ASSERT(cid < top_);
if (trace) {
trace_allocation_table_.load()[cid] |= 2;
} else {
trace_allocation_table_.load()[cid] &= ~2;
}
}
bool CollectInstancesFor(intptr_t cid) {
ASSERT(cid > 0);
ASSERT(cid < top_);
return (trace_allocation_table_.load()[cid] & 2) != 0;
}
#endif // !defined(PRODUCT)
void CopyBeforeHotReload(intptr_t** copy, intptr_t* copy_num_cids) {
+31 -4
View File
@@ -2395,10 +2395,6 @@ void Precompiler::TraceTypesFromRetainedClasses() {
while (it.HasNext()) {
cls = it.GetNextClass();
// The subclasses/implementors array is only needed for CHA.
cls.ClearDirectSubclasses();
cls.ClearDirectImplementors();
bool retain = false;
members = cls.fields();
if (members.Length() > 0) {
@@ -2549,6 +2545,12 @@ void Precompiler::DropLibraryEntries() {
void Precompiler::DropClasses() {
Class& cls = Class::Handle(Z);
Array& constants = Array::Handle(Z);
GrowableObjectArray& implementors = GrowableObjectArray::Handle(Z);
GrowableObjectArray& retained_implementors = GrowableObjectArray::Handle(Z);
Class& implementor = Class::Handle(Z);
GrowableObjectArray& subclasses = GrowableObjectArray::Handle(Z);
GrowableObjectArray& retained_subclasses = GrowableObjectArray::Handle(Z);
Class& subclass = Class::Handle(Z);
// We are about to remove classes from the class table. For this to be safe,
// there must be no instances of these classes on the heap, not even
@@ -2558,6 +2560,7 @@ void Precompiler::DropClasses() {
IG->heap()->CollectAllGarbage();
IG->heap()->WaitForSweeperTasks(T);
SafepointWriteRwLocker ml(T, IG->program_lock());
ClassTable* class_table = IG->class_table();
intptr_t num_cids = class_table->NumCids();
@@ -2576,6 +2579,30 @@ void Precompiler::DropClasses() {
cls = class_table->At(cid);
ASSERT(!cls.IsNull());
implementors = cls.direct_implementors();
if (!implementors.IsNull()) {
retained_implementors = GrowableObjectArray::New();
for (intptr_t i = 0; i < implementors.Length(); i++) {
implementor ^= implementors.At(i);
if (classes_to_retain_.HasKey(&implementor)) {
retained_implementors.Add(implementor);
}
}
cls.set_direct_implementors(retained_implementors);
}
subclasses = cls.direct_subclasses();
if (!subclasses.IsNull()) {
retained_subclasses = GrowableObjectArray::New();
for (intptr_t i = 0; i < subclasses.Length(); i++) {
subclass ^= subclasses.At(i);
if (classes_to_retain_.HasKey(&subclass)) {
retained_subclasses.Add(subclass);
}
}
cls.set_direct_subclasses(retained_subclasses);
}
if (cls.IsTopLevel()) {
// Top-level classes are referenced directly from their library. They
// will only be removed as a consequence of an entire library being
+7 -7
View File
@@ -2560,20 +2560,20 @@ void ProgramReloadContext::RebuildDirectSubclasses() {
// Clear the direct subclasses for all classes.
Class& cls = Class::Handle();
GrowableObjectArray& subclasses = GrowableObjectArray::Handle();
const GrowableObjectArray& null_list = GrowableObjectArray::Handle();
for (intptr_t i = 1; i < num_cids; i++) {
if (class_table->HasValidClassAt(i)) {
cls = class_table->At(i);
if (!cls.is_declaration_loaded()) {
continue; // Can't have any subclasses or implementors yet.
}
subclasses = cls.direct_subclasses();
if (!subclasses.IsNull()) {
cls.ClearDirectSubclasses();
// Testing for null to prevent attempting to write to read-only classes
// in the VM isolate.
if (cls.direct_subclasses() != GrowableObjectArray::null()) {
cls.set_direct_subclasses(null_list);
}
subclasses = cls.direct_implementors();
if (!subclasses.IsNull()) {
cls.ClearDirectImplementors();
if (cls.direct_implementors() != GrowableObjectArray::null()) {
cls.set_direct_implementors(null_list);
}
}
}
+5 -4
View File
@@ -5080,9 +5080,10 @@ void Class::AddDirectImplementor(const Class& implementor,
direct_implementors.Add(implementor, Heap::kOld);
}
void Class::ClearDirectImplementors() const {
void Class::set_direct_implementors(
const GrowableObjectArray& implementors) const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
untag()->set_direct_implementors(GrowableObjectArray::null());
untag()->set_direct_implementors(implementors.ptr());
}
void Class::AddDirectSubclass(const Class& subclass) const {
@@ -5106,9 +5107,9 @@ void Class::AddDirectSubclass(const Class& subclass) const {
direct_subclasses.Add(subclass, Heap::kOld);
}
void Class::ClearDirectSubclasses() const {
void Class::set_direct_subclasses(const GrowableObjectArray& subclasses) const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
untag()->set_direct_subclasses(GrowableObjectArray::null());
untag()->set_direct_subclasses(subclasses.ptr());
}
ArrayPtr Class::constants() const {
+5 -2
View File
@@ -1197,8 +1197,11 @@ class Class : public Object {
IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
return untag()->direct_implementors();
}
GrowableObjectArrayPtr direct_implementors_unsafe() const {
return untag()->direct_implementors();
}
void set_direct_implementors(const GrowableObjectArray& implementors) const;
void AddDirectImplementor(const Class& subclass, bool is_mixin) const;
void ClearDirectImplementors() const;
// Returns the list of classes having this class as direct superclass.
GrowableObjectArrayPtr direct_subclasses() const {
@@ -1209,8 +1212,8 @@ class Class : public Object {
GrowableObjectArrayPtr direct_subclasses_unsafe() const {
return untag()->direct_subclasses();
}
void set_direct_subclasses(const GrowableObjectArray& subclasses) const;
void AddDirectSubclass(const Class& subclass) const;
void ClearDirectSubclasses() const;
// Check if this class represents the class of null.
bool IsNullClass() const { return id() == kNullCid; }
+2
View File
@@ -934,7 +934,9 @@ class UntaggedClass : public UntaggedObject {
CompressedObjectPtr* to_snapshot(Snapshot::Kind kind) {
switch (kind) {
case Snapshot::kFullAOT:
#if defined(PRODUCT)
return reinterpret_cast<CompressedObjectPtr*>(&allocation_stub_);
#endif
case Snapshot::kFull:
case Snapshot::kFullCore:
return reinterpret_cast<CompressedObjectPtr*>(&direct_subclasses_);
+112 -10
View File
@@ -3079,23 +3079,72 @@ static bool EvaluateInFrame(Thread* thread, JSONStream* js) {
return true;
}
static void MarkClasses(const Class& root,
bool include_subclasses,
bool include_implementors) {
Thread* thread = Thread::Current();
HANDLESCOPE(thread);
SharedClassTable* table = thread->isolate()->group()->shared_class_table();
GrowableArray<const Class*> worklist;
table->SetCollectInstancesFor(root.id(), true);
worklist.Add(&root);
GrowableObjectArray& subclasses = GrowableObjectArray::Handle();
GrowableObjectArray& implementors = GrowableObjectArray::Handle();
while (!worklist.is_empty()) {
const Class& cls = *worklist.RemoveLast();
// All subclasses are implementors, but they are not included in
// `direct_implementors`.
if (include_subclasses || include_implementors) {
subclasses = cls.direct_subclasses_unsafe();
if (!subclasses.IsNull()) {
for (intptr_t j = 0; j < subclasses.Length(); j++) {
Class& subclass = Class::Handle();
subclass ^= subclasses.At(j);
if (!table->CollectInstancesFor(subclass.id())) {
table->SetCollectInstancesFor(subclass.id(), true);
worklist.Add(&subclass);
}
}
}
}
if (include_implementors) {
implementors = cls.direct_implementors_unsafe();
if (!implementors.IsNull()) {
for (intptr_t j = 0; j < implementors.Length(); j++) {
Class& implementor = Class::Handle();
implementor ^= implementors.At(j);
if (!table->CollectInstancesFor(implementor.id())) {
table->SetCollectInstancesFor(implementor.id(), true);
worklist.Add(&implementor);
}
}
}
}
}
}
static void UnmarkClasses() {
SharedClassTable* table = IsolateGroup::Current()->shared_class_table();
for (intptr_t i = 1; i < table->NumCids(); i++) {
table->SetCollectInstancesFor(i, false);
}
}
class GetInstancesVisitor : public ObjectGraph::Visitor {
public:
GetInstancesVisitor(const Class& cls,
ZoneGrowableHandlePtrArray<Object>* storage,
GetInstancesVisitor(ZoneGrowableHandlePtrArray<Object>* storage,
intptr_t limit)
: cls_(cls), storage_(storage), limit_(limit), count_(0) {}
: table_(IsolateGroup::Current()->shared_class_table()),
storage_(storage),
limit_(limit),
count_(0) {}
virtual Direction VisitObject(ObjectGraph::StackIterator* it) {
ObjectPtr raw_obj = it->Get();
if (raw_obj->IsPseudoObject()) {
return kProceed;
}
Thread* thread = Thread::Current();
REUSABLE_OBJECT_HANDLESCOPE(thread);
Object& obj = thread->ObjectHandle();
obj = raw_obj;
if (obj.GetClassId() == cls_.id()) {
if (table_->CollectInstancesFor(raw_obj->GetClassId())) {
if (count_ < limit_) {
storage_->Add(Object::Handle(raw_obj));
}
@@ -3107,7 +3156,7 @@ class GetInstancesVisitor : public ObjectGraph::Visitor {
intptr_t count() const { return count_; }
private:
const Class& cls_;
SharedClassTable* const table_;
ZoneGrowableHandlePtrArray<Object>* storage_;
const intptr_t limit_;
intptr_t count_;
@@ -3147,11 +3196,13 @@ static bool GetInstances(Thread* thread, JSONStream* js) {
HANDLESCOPE(thread);
ZoneGrowableHandlePtrArray<Object> storage(thread->zone(), limit);
GetInstancesVisitor visitor(cls, &storage, limit);
GetInstancesVisitor visitor(&storage, limit);
{
ObjectGraph graph(thread);
HeapIterationScope iteration_scope(Thread::Current(), true);
MarkClasses(cls, false, false);
graph.IterateObjects(&visitor);
UnmarkClasses();
}
intptr_t count = visitor.count();
JSONObject jsobj(js);
@@ -3166,6 +3217,55 @@ static bool GetInstances(Thread* thread, JSONStream* js) {
return true;
}
static const MethodParameter* get_instances_as_array_params[] = {
RUNNABLE_ISOLATE_PARAMETER,
NULL,
};
static bool GetInstancesAsArray(Thread* thread, JSONStream* js) {
const char* object_id = js->LookupParam("objectId");
if (object_id == NULL) {
PrintMissingParamError(js, "objectId");
return true;
}
bool include_subclasses =
BoolParameter::Parse(js->LookupParam("includeSubclasses"), false);
bool include_implementors =
BoolParameter::Parse(js->LookupParam("includeImplementors"), false);
const Object& obj = Object::Handle(LookupHeapObject(thread, object_id, NULL));
if (obj.ptr() == Object::sentinel().ptr() || !obj.IsClass()) {
PrintInvalidParamError(js, "objectId");
return true;
}
const Class& cls = Class::Cast(obj);
// Ensure the array and handles created below are promptly destroyed.
Array& instances = Array::Handle();
{
StackZone zone(thread);
HANDLESCOPE(thread);
ZoneGrowableHandlePtrArray<Object> storage(thread->zone(), 1024);
GetInstancesVisitor visitor(&storage, kSmiMax);
{
ObjectGraph graph(thread);
HeapIterationScope iteration_scope(Thread::Current(), true);
MarkClasses(cls, include_subclasses, include_implementors);
graph.IterateObjects(&visitor);
UnmarkClasses();
}
intptr_t count = visitor.count();
instances = Array::New(count);
for (intptr_t i = 0; i < count; i++) {
instances.SetAt(i, storage.At(i));
}
}
instances.PrintJSON(js, /* as_ref */ true);
return true;
}
static const MethodParameter* get_ports_params[] = {
RUNNABLE_ISOLATE_PARAMETER,
NULL,
@@ -5116,6 +5216,8 @@ static const ServiceMethodDescriptor service_methods_[] = {
get_inbound_references_params },
{ "getInstances", GetInstances,
get_instances_params },
{ "_getInstancesAsArray", GetInstancesAsArray,
get_instances_as_array_params },
{ "getPorts", GetPorts,
get_ports_params },
{ "getIsolate", GetIsolate,