diff --git a/runtime/observatory/lib/src/elements/class_instances.dart b/runtime/observatory/lib/src/elements/class_instances.dart index abf90ccfa1e..8d12e212146 100644 --- a/runtime/observatory/lib/src/elements/class_instances.dart +++ b/runtime/observatory/lib/src/elements/class_instances.dart @@ -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? _allInstances = null; + bool _loadingAllInstances = false; + M.Guarded? _allSubclassInstances = null; + bool _loadingAllSubclassInstances = false; + M.Guarded? _allImplementorInstances = null; + bool _loadingAllImplementorInstances = false; M.Guarded? _retainedSize = null; bool _loadingRetainedBytes = false; M.Guarded? _reachableSize = null; @@ -106,6 +113,41 @@ class ClassInstancesElement extends CustomElement implements Renderable { ..classes = ['memberValue'] ..children = [_strong!.element] ], + new DivElement() + ..classes = ['memberItem'] + ..children = [ + 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 = [ + 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 = [ + 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 _createAllInstances() { + final content = []; + 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 _createAllSubclassInstances() { + final content = []; + 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 _createAllImplementorInstances() { + final content = []; + 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 _createReachableSizeValue() { final content = []; 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); diff --git a/runtime/observatory/lib/src/models/repositories/strongly_reachable_instances.dart b/runtime/observatory/lib/src/models/repositories/strongly_reachable_instances.dart index c0449f82f08..195948a4780 100644 --- a/runtime/observatory/lib/src/models/repositories/strongly_reachable_instances.dart +++ b/runtime/observatory/lib/src/models/repositories/strongly_reachable_instances.dart @@ -6,4 +6,6 @@ part of models; abstract class StronglyReachableInstancesRepository { Future get(IsolateRef isolate, ClassRef cls, {int limit: 100}); + Future> getAsArray(IsolateRef isolate, ClassRef cls, + {bool includeSubclasses: false, includeImplementors: false}); } diff --git a/runtime/observatory/lib/src/repositories/strongly_reachable_instances.dart b/runtime/observatory/lib/src/repositories/strongly_reachable_instances.dart index a015928458a..f4ed7365837 100644 --- a/runtime/observatory/lib/src/repositories/strongly_reachable_instances.dart +++ b/runtime/observatory/lib/src/repositories/strongly_reachable_instances.dart @@ -15,4 +15,18 @@ class StronglyReachableInstancesRepository assert(limit != null); return (await isolate.getInstances(cls, limit)) as S.InstanceSet; } + + Future> 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(response); + } } diff --git a/runtime/observatory/tests/service/get_instances_as_array_rpc_test.dart b/runtime/observatory/tests/service/get_instances_as_array_rpc_test.dart new file mode 100644 index 00000000000..1981ec9c3d8 --- /dev/null +++ b/runtime/observatory/tests/service/get_instances_as_array_rpc_test.dart @@ -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 = [ + (Isolate isolate) async { + invoke(String selector) async { + Map params = { + "targetId": isolate.rootLibrary.id, + "selector": selector, + "argumentIds": [], + }; + return await isolate.invokeRpcNoUpgrade("invoke", params); + } + + Future 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); diff --git a/runtime/observatory_2/lib/src/elements/class_instances.dart b/runtime/observatory_2/lib/src/elements/class_instances.dart index bc3e101bca5..7197780bc5b 100644 --- a/runtime/observatory_2/lib/src/elements/class_instances.dart +++ b/runtime/observatory_2/lib/src/elements/class_instances.dart @@ -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 _allInstances = null; + bool _loadingAllInstances = false; + M.Guarded _allSubclassInstances = null; + bool _loadingAllSubclassInstances = false; + M.Guarded _allImplementorInstances = null; + bool _loadingAllImplementorInstances = false; M.Guarded _retainedSize = null; bool _loadingRetainedBytes = false; M.Guarded _reachableSize = null; @@ -106,6 +113,41 @@ class ClassInstancesElement extends CustomElement implements Renderable { ..classes = ['memberValue'] ..children = [_strong.element] ], + new DivElement() + ..classes = ['memberItem'] + ..children = [ + 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 = [ + 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 = [ + 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 _createAllInstances() { + final content = []; + 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 _createAllSubclassInstances() { + final content = []; + 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 _createAllImplementorInstances() { + final content = []; + 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 _createReachableSizeValue() { final content = []; if (_reachableSize != null) { diff --git a/runtime/observatory_2/lib/src/models/repositories/strongly_reachable_instances.dart b/runtime/observatory_2/lib/src/models/repositories/strongly_reachable_instances.dart index c0449f82f08..195948a4780 100644 --- a/runtime/observatory_2/lib/src/models/repositories/strongly_reachable_instances.dart +++ b/runtime/observatory_2/lib/src/models/repositories/strongly_reachable_instances.dart @@ -6,4 +6,6 @@ part of models; abstract class StronglyReachableInstancesRepository { Future get(IsolateRef isolate, ClassRef cls, {int limit: 100}); + Future> getAsArray(IsolateRef isolate, ClassRef cls, + {bool includeSubclasses: false, includeImplementors: false}); } diff --git a/runtime/observatory_2/lib/src/repositories/strongly_reachable_instances.dart b/runtime/observatory_2/lib/src/repositories/strongly_reachable_instances.dart index a015928458a..8083ea780f5 100644 --- a/runtime/observatory_2/lib/src/repositories/strongly_reachable_instances.dart +++ b/runtime/observatory_2/lib/src/repositories/strongly_reachable_instances.dart @@ -15,4 +15,18 @@ class StronglyReachableInstancesRepository assert(limit != null); return (await isolate.getInstances(cls, limit)) as S.InstanceSet; } + + Future> 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(response); + } } diff --git a/runtime/observatory_2/tests/service_2/get_instances_as_array_rpc_test.dart b/runtime/observatory_2/tests/service_2/get_instances_as_array_rpc_test.dart new file mode 100644 index 00000000000..f87d8973269 --- /dev/null +++ b/runtime/observatory_2/tests/service_2/get_instances_as_array_rpc_test.dart @@ -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 = [ + (Isolate isolate) async { + invoke(String selector) async { + Map params = { + "targetId": isolate.rootLibrary.id, + "selector": selector, + "argumentIds": [], + }; + return await isolate.invokeRpcNoUpgrade("invoke", params); + } + + Future 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); diff --git a/runtime/vm/class_table.h b/runtime/vm/class_table.h index 9f238200fd7..3f0d49f7f4a 100644 --- a/runtime/vm/class_table.h +++ b/runtime/vm/class_table.h @@ -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) { diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc index e84949fe6c6..6b87632758d 100644 --- a/runtime/vm/compiler/aot/precompiler.cc +++ b/runtime/vm/compiler/aot/precompiler.cc @@ -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 diff --git a/runtime/vm/isolate_reload.cc b/runtime/vm/isolate_reload.cc index a5bd97fa5ce..f5cc8097d38 100644 --- a/runtime/vm/isolate_reload.cc +++ b/runtime/vm/isolate_reload.cc @@ -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); } } } diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index 8c9f0ea9c90..2c1485453c6 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -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 { diff --git a/runtime/vm/object.h b/runtime/vm/object.h index 6848241eb5a..2a7efb4cca9 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -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; } diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h index 5776c386533..f8e344b1433 100644 --- a/runtime/vm/raw_object.h +++ b/runtime/vm/raw_object.h @@ -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(&allocation_stub_); +#endif case Snapshot::kFull: case Snapshot::kFullCore: return reinterpret_cast(&direct_subclasses_); diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc index ee7a7ec88e3..5da2074575d 100644 --- a/runtime/vm/service.cc +++ b/runtime/vm/service.cc @@ -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 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* storage, + GetInstancesVisitor(ZoneGrowableHandlePtrArray* 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* storage_; const intptr_t limit_; intptr_t count_; @@ -3147,11 +3196,13 @@ static bool GetInstances(Thread* thread, JSONStream* js) { HANDLESCOPE(thread); ZoneGrowableHandlePtrArray 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 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,