From 170bda2b74099aa892992e3936b5c545b76ee7bb Mon Sep 17 00:00:00 2001 From: Ryan Macnak Date: Thu, 1 Apr 2021 20:53:22 +0000 Subject: [PATCH] [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 Reviewed-by: Stephen Adams --- .../lib/src/elements/class_instances.dart | 134 ++++++++++++++++++ .../strongly_reachable_instances.dart | 2 + .../strongly_reachable_instances.dart | 14 ++ .../get_instances_as_array_rpc_test.dart | 75 ++++++++++ .../lib/src/elements/class_instances.dart | 131 +++++++++++++++++ .../strongly_reachable_instances.dart | 2 + .../strongly_reachable_instances.dart | 14 ++ .../get_instances_as_array_rpc_test.dart | 72 ++++++++++ runtime/vm/class_table.h | 14 ++ runtime/vm/compiler/aot/precompiler.cc | 35 ++++- runtime/vm/isolate_reload.cc | 14 +- runtime/vm/object.cc | 9 +- runtime/vm/object.h | 7 +- runtime/vm/raw_object.h | 2 + runtime/vm/service.cc | 122 ++++++++++++++-- 15 files changed, 620 insertions(+), 27 deletions(-) create mode 100644 runtime/observatory/tests/service/get_instances_as_array_rpc_test.dart create mode 100644 runtime/observatory_2/tests/service_2/get_instances_as_array_rpc_test.dart 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,