From fd2a3cdc3a4e5b44d989f957eb4acc6b4f77fafb Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Tue, 17 Aug 2021 16:14:02 +0000 Subject: [PATCH 1/5] Support navigation to macro-generated elements. We use previously added `CompilationUnitElementImpl.macroGeneratedContent` to access the current combined content. In order to make this work with current IntelliJ plugin, without adding new protocols, we store the generated code into a new file, and locations for macro-generated elements are in this file. Locations for elements that are not macro-generated are still in the source file. We use `ElementLocationProvider` to separate writing to the file system, and do this only in DAS. Change-Id: Ib156c67b0bf1f3474eb06584e76aa532abd597d1 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/210300 Reviewed-by: Brian Wilkerson Commit-Queue: Konstantin Shcheglov --- .../lib/src/analysis_server.dart | 7 +- .../lib/src/domains/analysis/macro_files.dart | 121 ++++++++++++++++++ .../notification_navigation_test.dart | 58 +++++++++ .../lib/utilities/analyzer_converter.dart | 19 +++ .../utilities/navigation/navigation_dart.dart | 35 +++-- 5 files changed, 225 insertions(+), 15 deletions(-) create mode 100644 pkg/analysis_server/lib/src/domains/analysis/macro_files.dart diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart index 389690580a5c9..38529f2a57bd5 100644 --- a/pkg/analysis_server/lib/src/analysis_server.dart +++ b/pkg/analysis_server/lib/src/analysis_server.dart @@ -24,6 +24,7 @@ import 'package:analysis_server/src/domain_diagnostic.dart'; import 'package:analysis_server/src/domain_execution.dart'; import 'package:analysis_server/src/domain_kythe.dart'; import 'package:analysis_server/src/domain_server.dart'; +import 'package:analysis_server/src/domains/analysis/macro_files.dart'; import 'package:analysis_server/src/domains/analysis/occurrences.dart'; import 'package:analysis_server/src/domains/analysis/occurrences_dart.dart'; import 'package:analysis_server/src/edit/edit_domain.dart'; @@ -54,6 +55,7 @@ import 'package:analyzer/src/generated/sdk.dart'; import 'package:analyzer/src/util/file_paths.dart' as file_paths; import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element; import 'package:analyzer_plugin/src/utilities/navigation/navigation.dart'; +import 'package:analyzer_plugin/utilities/analyzer_converter.dart'; import 'package:analyzer_plugin/utilities/navigation/navigation_dart.dart'; import 'package:http/http.dart' as http; import 'package:telemetry/crash_reporting.dart'; @@ -718,7 +720,10 @@ class ServerContextManagerCallbacks extends ContextManagerCallbacks { server.AnalysisNavigationParams _computeNavigationParams( String path, CompilationUnit unit) { var collector = NavigationCollectorImpl(); - computeDartNavigation(resourceProvider, collector, unit, null, null); + computeDartNavigation(resourceProvider, collector, unit, null, null, + analyzerConverter: AnalyzerConverter( + locationProvider: + MacroElementLocationProvider(MacroFiles(resourceProvider)))); collector.createRegions(); return server.AnalysisNavigationParams( path, collector.regions, collector.targets, collector.files); diff --git a/pkg/analysis_server/lib/src/domains/analysis/macro_files.dart b/pkg/analysis_server/lib/src/domains/analysis/macro_files.dart new file mode 100644 index 0000000000000..be391bee1a850 --- /dev/null +++ b/pkg/analysis_server/lib/src/domains/analysis/macro_files.dart @@ -0,0 +1,121 @@ +// Copyright (c) 2021, 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:analyzer/dart/element/element.dart'; +import 'package:analyzer/file_system/file_system.dart'; +import 'package:analyzer/source/line_info.dart'; +import 'package:analyzer/src/dart/element/element.dart'; +import 'package:analyzer/src/util/file_paths.dart' as file_paths; +import 'package:analyzer_plugin/protocol/protocol_common.dart' as protocol; +import 'package:analyzer_plugin/utilities/analyzer_converter.dart'; + +class MacroElementLocationProvider implements ElementLocationProvider { + final MacroFiles _macroFiles; + + MacroElementLocationProvider(this._macroFiles); + + @override + protocol.Location? forElement(Element element) { + if (element is HasMacroGenerationData) { + var macro = (element as HasMacroGenerationData).macro; + if (macro != null) { + return _forElement(element, macro); + } + } + } + + protocol.Location? _forElement(Element element, MacroGenerationData macro) { + var unitElement = element.thisOrAncestorOfType(); + if (unitElement is! CompilationUnitElementImpl) { + return null; + } + + var generatedFile = _macroFiles.generatedFile(unitElement); + if (generatedFile == null) { + return null; + } + + var nameOffset = element.nameOffset; + var nameLength = element.nameLength; + + var lineInfo = generatedFile.lineInfo; + var offsetLocation = lineInfo.getLocation(nameOffset); + var endLocation = lineInfo.getLocation(nameOffset + nameLength); + + return protocol.Location(generatedFile.path, nameOffset, nameLength, + offsetLocation.lineNumber, offsetLocation.columnNumber, + endLine: endLocation.lineNumber, endColumn: endLocation.columnNumber); + } +} + +/// Note, this class changes the file system. +class MacroFiles { + final ResourceProvider _resourceProvider; + + /// Keys are source paths. + final Map _files = {}; + + MacroFiles(this._resourceProvider); + + /// If [unitElement] has macro-generated elements, write the combined + /// content into a new file in `.dart_tool`, and return the description of + /// this file. + _MacroGeneratedFile? generatedFile(CompilationUnitElementImpl unitElement) { + var sourcePath = unitElement.source.fullName; + + var result = _files[sourcePath]; + if (result != null) { + return result; + } + + var sourceFile = _resourceProvider.getFile(sourcePath); + + // TODO(scheglov) Use workspace? + Folder? packageRoot; + for (var parent in sourceFile.parent2.withAncestors) { + if (parent.getChildAssumingFile(file_paths.pubspecYaml).exists) { + packageRoot = parent; + break; + } + } + if (packageRoot == null) { + return null; + } + + var pathContext = _resourceProvider.pathContext; + var relativePath = pathContext.relative( + sourcePath, + from: packageRoot.path, + ); + var generatedPath = pathContext.join( + packageRoot.path, '.dart_tool', 'analyzer', 'macro', relativePath); + + var generatedContent = unitElement.macroGeneratedContent; + if (generatedContent == null) { + return null; + } + + try { + _resourceProvider.getFile(generatedPath) + ..parent2.create() + ..writeAsStringSync(generatedContent); + } on FileSystemException { + return null; + } + + return _files[sourcePath] = _MacroGeneratedFile( + generatedPath, + generatedContent, + ); + } +} + +class _MacroGeneratedFile { + final String path; + final String content; + final LineInfo lineInfo; + + _MacroGeneratedFile(this.path, this.content) + : lineInfo = LineInfo.fromContent(content); +} diff --git a/pkg/analysis_server/test/analysis/notification_navigation_test.dart b/pkg/analysis_server/test/analysis/notification_navigation_test.dart index bdd6ad3f6bade..8a8f918f80ba1 100644 --- a/pkg/analysis_server/test/analysis/notification_navigation_test.dart +++ b/pkg/analysis_server/test/analysis/notification_navigation_test.dart @@ -775,6 +775,64 @@ library my.lib; assertHasTargetString('my.lib'); } + Future test_macro_simpleIdentifier_getter() async { + // TODO(scheglov) Use PubPackageResolutionTest? + newFile('$projectPath/pubspec.yaml', content: ''); + + newFile('$projectPath/bin/macro_annotations.dart', content: r''' +library analyzer.macro.annotations; +const observable = 0; +'''); + + addTestFile(r''' +import 'macro_annotations.dart'; + +class A { + @observable + int _foo = 0; +} + +void f(A a) { + a.foo; +} +'''); + + await prepareNavigation(); + assertHasRegionString('foo;', 3); + + var generatedFile = getFile( + '/project/.dart_tool/analyzer/macro/bin/test.dart', + ); + + var generatedContent = generatedFile.readAsStringSync(); + // TODO(scheglov) Improve macro to be more formatted. + expect(generatedContent, r''' +import 'macro_annotations.dart'; + +class A { + @observable + int _foo = 0; + +int get foo => _foo; + +set foo(int val) { + print('Setting foo to ${val}'); + _foo = val; +} +} + +void f(A a) { + a.foo; +} +'''); + + assertHasFileTarget( + generatedFile.path, + generatedContent.indexOf('foo =>'), + 'foo'.length, + ); + } + Future test_multiplyDefinedElement() async { newFile('$projectPath/bin/libA.dart', content: 'library A; int TEST = 1;'); newFile('$projectPath/bin/libB.dart', content: 'library B; int TEST = 2;'); diff --git a/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart b/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart index 575cd5b1fcb81..10640fc5b6a0d 100644 --- a/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart +++ b/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart @@ -17,6 +17,13 @@ import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin; /// /// Clients may not extend, implement or mix-in this class. class AnalyzerConverter { + /// Optional provider for [analyzer.Element] location, used for example to + /// override the location of macro-generated elements. + final ElementLocationProvider? _locationProvider; + + AnalyzerConverter({ElementLocationProvider? locationProvider}) + : _locationProvider = locationProvider; + /// Convert the analysis [error] from the 'analyzer' package to an analysis /// error defined by the plugin API. If a [lineInfo] is provided then the /// error's location will have a start line and start column. If a [severity] @@ -199,6 +206,12 @@ class AnalyzerConverter { if (element == null || element.source == null) { return null; } + + var result = _locationProvider?.forElement(element); + if (result != null) { + return result; + } + offset ??= element.nameOffset; length ??= element.nameLength; if (element is analyzer.CompilationUnitElement || @@ -412,3 +425,9 @@ class AnalyzerConverter { endLine: endLine, endColumn: endColumn); } } + +abstract class ElementLocationProvider { + /// Return the location of [element] that this provider wants to override, + /// or `null` if the default location should be used. + plugin.Location? forElement(analyzer.Element element); +} diff --git a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart index e0a5372eb5ad7..557e7c1737fee 100644 --- a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart +++ b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart @@ -16,12 +16,15 @@ import 'package:analyzer_plugin/utilities/analyzer_converter.dart'; import 'package:analyzer_plugin/utilities/navigation/navigation.dart'; NavigationCollector computeDartNavigation( - ResourceProvider resourceProvider, - NavigationCollector collector, - CompilationUnit unit, - int? offset, - int? length) { - var dartCollector = _DartNavigationCollector(collector, offset, length); + ResourceProvider resourceProvider, + NavigationCollector collector, + CompilationUnit unit, + int? offset, + int? length, { + AnalyzerConverter? analyzerConverter, +}) { + var dartCollector = _DartNavigationCollector( + collector, offset, length, analyzerConverter ?? AnalyzerConverter()); var visitor = _DartNavigationComputerVisitor(resourceProvider, dartCollector); if (offset == null || length == null) { unit.accept(visitor); @@ -66,9 +69,14 @@ class _DartNavigationCollector { final NavigationCollector collector; final int? requestedOffset; final int? requestedLength; + final AnalyzerConverter _analyzerConverter; _DartNavigationCollector( - this.collector, this.requestedOffset, this.requestedLength); + this.collector, + this.requestedOffset, + this.requestedLength, + this._analyzerConverter, + ); void _addRegion(int offset, int length, Element? element) { element = element?.nonSynthetic; @@ -85,15 +93,14 @@ class _DartNavigationCollector { if (!_isWithinRequestedRange(offset, length)) { return; } - var converter = AnalyzerConverter(); - var kind = converter.convertElementKind(element.kind); - var location = converter.locationFromElement(element); + var kind = _analyzerConverter.convertElementKind(element.kind); + var location = _analyzerConverter.locationFromElement(element); if (location == null) { return; } var codeLocation = collector.collectCodeLocations - ? _getCodeLocation(element, location, converter) + ? _getCodeLocation(element, location) : null; collector.addRegion(offset, length, kind, location, @@ -122,8 +129,8 @@ class _DartNavigationCollector { } /// Get the location of the code (excluding leading doc comments) for this element. - protocol.Location? _getCodeLocation(Element element, - protocol.Location location, AnalyzerConverter converter) { + protocol.Location? _getCodeLocation( + Element element, protocol.Location location) { var codeElement = element; // For synthetic getters created for fields, we need to access the associated // variable to get the codeOffset/codeLength. @@ -162,7 +169,7 @@ class _DartNavigationCollector { codeOffset = offsetAfterDocs; } - return converter.locationFromElement(element, + return _analyzerConverter.locationFromElement(element, offset: codeOffset, length: codeLength); } From 8bb5ced9867e9bf16583aea295cace251453f231 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 17 Aug 2021 16:14:42 +0000 Subject: [PATCH 2/5] add a benchmark tracking the dart sdk size Change-Id: Ibbd23a3c376584711515462620e1a22254214181 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/210289 Reviewed-by: Ben Konyi Commit-Queue: Devon Carew --- .../dart/SDKArtifactSizes.dart | 20 +++++++++++++++++++ .../dart2/SDKArtifactSizes.dart | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/benchmarks/SDKArtifactSizes/dart/SDKArtifactSizes.dart b/benchmarks/SDKArtifactSizes/dart/SDKArtifactSizes.dart index ece1bcc5c14db..5987a508ed688 100644 --- a/benchmarks/SDKArtifactSizes/dart/SDKArtifactSizes.dart +++ b/benchmarks/SDKArtifactSizes/dart/SDKArtifactSizes.dart @@ -63,4 +63,24 @@ Future main() async { '$rootDir/dart-sdk/bin/snapshots/$snapshot.dart.snapshot'; await reportArtifactSize(snapshotPath, snapshot); } + + // Measure the (compressed) sdk size. + final tempDir = Directory.systemTemp.createTempSync('dartdev'); + final sdkArchive = + compress(File(Platform.resolvedExecutable).parent.parent, tempDir); + await reportArtifactSize(sdkArchive?.path, 'sdk'); + tempDir.deleteSync(recursive: true); +} + +File compress(Directory sourceDir, Directory targetDir) { + final outFile = File('${targetDir.path}/sdk.zip'); + + if (Platform.isMacOS || Platform.isLinux) { + Process.runSync( + 'zip', ['-r', outFile.absolute.path, sourceDir.absolute.path]); + } else { + return null; + } + + return outFile; } diff --git a/benchmarks/SDKArtifactSizes/dart2/SDKArtifactSizes.dart b/benchmarks/SDKArtifactSizes/dart2/SDKArtifactSizes.dart index ece1bcc5c14db..0cefa4d4bdc30 100644 --- a/benchmarks/SDKArtifactSizes/dart2/SDKArtifactSizes.dart +++ b/benchmarks/SDKArtifactSizes/dart2/SDKArtifactSizes.dart @@ -63,4 +63,24 @@ Future main() async { '$rootDir/dart-sdk/bin/snapshots/$snapshot.dart.snapshot'; await reportArtifactSize(snapshotPath, snapshot); } + + // Measure the (compressed) sdk size. + final tempDir = Directory.systemTemp.createTempSync('dartdev'); + final sdkArchive = + compress(File(Platform.resolvedExecutable).parent.parent, tempDir); + await reportArtifactSize(sdkArchive?.path ?? '', 'sdk'); + tempDir.deleteSync(recursive: true); +} + +File? compress(Directory sourceDir, Directory targetDir) { + final outFile = File('${targetDir.path}/sdk.zip'); + + if (Platform.isMacOS || Platform.isLinux) { + Process.runSync( + 'zip', ['-r', outFile.absolute.path, sourceDir.absolute.path]); + } else { + return null; + } + + return outFile; } From d9048340ba899dc00d2800ab311d79d23a73063f Mon Sep 17 00:00:00 2001 From: Ryan Macnak Date: Tue, 17 Aug 2021 16:49:32 +0000 Subject: [PATCH 3/5] [vm, service] Include each isolate a separate synthetic object in heap snapshots. Fix labels for sentinel, transition_sentinel, ImmutableArray, LinkedHashMap and LinkedHashSet. Display unlimited children when look at successors, as the worst case is the size of the field table. For predecessors and dominators, the worst case is the whole heap. TEST=ci Change-Id: I6fcfdfb0833d58c9ac4f586b823244d817aeba27 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/209841 Commit-Queue: Ryan Macnak Reviewed-by: Alexander Aprelev --- .../lib/src/elements/heap_snapshot.dart | 2 +- .../observatory/lib/src/service/object.dart | 8 +- .../object_graph_isolate_group_test.dart | 70 ++++++++ .../tests/service/object_graph_vm_test.dart | 8 +- .../lib/src/elements/heap_snapshot.dart | 2 +- .../observatory_2/lib/src/service/object.dart | 4 - .../object_graph_isolate_group_test.dart | 72 ++++++++ .../tests/service_2/object_graph_vm_test.dart | 8 +- runtime/vm/isolate.cc | 24 ++- runtime/vm/isolate.h | 1 + runtime/vm/object_graph.cc | 160 +++++++++++++++--- runtime/vm/raw_object_fields.cc | 20 ++- 12 files changed, 325 insertions(+), 54 deletions(-) create mode 100644 runtime/observatory/tests/service/object_graph_isolate_group_test.dart create mode 100644 runtime/observatory_2/tests/service_2/object_graph_isolate_group_test.dart diff --git a/runtime/observatory/lib/src/elements/heap_snapshot.dart b/runtime/observatory/lib/src/elements/heap_snapshot.dart index 1ddb59eac51e5..e83ec9ea5bd1d 100644 --- a/runtime/observatory/lib/src/elements/heap_snapshot.dart +++ b/runtime/observatory/lib/src/elements/heap_snapshot.dart @@ -1197,7 +1197,7 @@ class HeapSnapshotElement extends CustomElement implements Renderable { static Iterable _getChildrenSuccessor(nodeDynamic) { SnapshotObject node = nodeDynamic; - return node.successors.take(kMaxChildren).toList(); + return node.successors.toList(); } static Iterable _getChildrenPredecessor(nodeDynamic) { diff --git a/runtime/observatory/lib/src/service/object.dart b/runtime/observatory/lib/src/service/object.dart index 7d83c714e733c..df4a4cd8fe4cb 100644 --- a/runtime/observatory/lib/src/service/object.dart +++ b/runtime/observatory/lib/src/service/object.dart @@ -676,8 +676,6 @@ abstract class VM extends ServiceObjectOwner implements M.VM { String targetCPU = 'unknown'; String embedder = 'unknown'; int architectureBits = 0; - bool assertsEnabled = false; - bool typeChecksEnabled = false; int nativeZoneMemoryUsage = 0; int pid = 0; int mallocUsed = 0; @@ -1050,8 +1048,6 @@ abstract class VM extends ServiceObjectOwner implements M.VM { maxRSS = map['_maxRSS']; currentRSS = map['_currentRSS']; profileVM = map['_profilerMode'] == 'VM'; - assertsEnabled = map['_assertsEnabled']; - typeChecksEnabled = map['_typeChecksEnabled']; _removeDeadIsolates([ ...map['isolates'], ...map['systemIsolates'], @@ -1550,7 +1546,7 @@ class Isolate extends ServiceObjectOwner implements M.Isolate { // There are sometimes isolate refs in ServiceEvents. return vm.getFromMap(map); } - String mapId = map['id']; + String? mapId = map['id']; var obj = (mapId != null) ? _cache[mapId] : null; if (obj != null) { obj.updateFromServiceMap(map); @@ -1559,7 +1555,7 @@ class Isolate extends ServiceObjectOwner implements M.Isolate { // Build the object from the map directly. obj = ServiceObject._fromMap(this, map); if ((obj != null) && obj.canCache) { - _cache[mapId] = obj; + _cache[mapId!] = obj; } return obj; } diff --git a/runtime/observatory/tests/service/object_graph_isolate_group_test.dart b/runtime/observatory/tests/service/object_graph_isolate_group_test.dart new file mode 100644 index 0000000000000..d325f755a0f74 --- /dev/null +++ b/runtime/observatory/tests/service/object_graph_isolate_group_test.dart @@ -0,0 +1,70 @@ +// Copyright (c) 2021, 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. + +// VMOptions=--enable_isolate_groups + +import 'dart:isolate' as isolate; +import 'package:observatory/object_graph.dart'; +import 'package:observatory/service_io.dart'; +import 'package:test/test.dart'; +import 'test_helper.dart'; + +// Make sure these fields are not removed by the tree shaker. +@pragma("vm:entry-point") +dynamic bigGlobal; + +child(message) { + var bigString = message[0] as String; + var replyPort = message[1] as isolate.SendPort; + bigGlobal = bigString; + replyPort.send(null); + new isolate.RawReceivePort(); // Keep child alive. +} + +void script() { + var bigString = "x" * (1 << 20); + var port; + for (var i = 0; i < 2; i++) { + port = new isolate.RawReceivePort((_) => port.close()); + isolate.Isolate.spawn(child, [bigString, port.sendPort]); + } + bigGlobal = bigString; + print("Ready"); +} + +var tests = [ + (Isolate isolate) async { + var graph = await isolate.fetchHeapSnapshot().done; + + // We are assuming the big string is the largest in the heap, and that it + // was shared/pass-by-pointer. + List strings = graph.objects + .where((SnapshotObject obj) => obj.klass.name == "_OneByteString") + .toList(); + strings.sort((u, v) => v.shallowSize - u.shallowSize); + SnapshotObject bigString = strings[0]; + print("bigString: $bigString"); + expect(bigString.shallowSize, greaterThanOrEqualTo(1 << 20)); + + int matchingPredecessors = 0; + for (SnapshotObject predecessor in bigString.predecessors) { + print("predecessor $predecessor ${predecessor.label}"); + if (predecessor.label.contains("bigGlobal") && + predecessor.klass.name.contains("Isolate")) { + matchingPredecessors++; + } + } + + for (SnapshotObject object in graph.objects) { + if (object.klass.name.contains("Isolate")) { + print("$object / ${object.description}"); + } + } + + // Parent and two children. Seeing all 3 means we visited all the field tables. + expect(matchingPredecessors, equals(3)); + } +]; + +main(args) => runIsolateTests(args, tests, testeeBefore: script); diff --git a/runtime/observatory/tests/service/object_graph_vm_test.dart b/runtime/observatory/tests/service/object_graph_vm_test.dart index a4bf3fec15fe6..8a8e7ec81fd76 100644 --- a/runtime/observatory/tests/service/object_graph_vm_test.dart +++ b/runtime/observatory/tests/service/object_graph_vm_test.dart @@ -90,8 +90,8 @@ var tests = [ int internalSum = 0; int externalSum = 0; for (SnapshotObject instance in klass.instances) { - if (instance == graph.root) { - // The root may have 0 self size. + if (instance == graph.root || instance.klass.name.contains("Isolate")) { + // The root and fake root subdivisions have 0 self size. expect(instance.internalSize, greaterThanOrEqualTo(0)); expect(instance.externalSize, greaterThanOrEqualTo(0)); expect(instance.shallowSize, greaterThanOrEqualTo(0)); @@ -122,8 +122,8 @@ var tests = [ int internalSum = 0; int externalSum = 0; for (SnapshotObject instance in graph.objects) { - if (instance == graph.root) { - // The root may have 0 self size. + if (instance == graph.root || instance.klass.name.contains("Isolate")) { + // The root and fake root subdivisions have 0 self size. expect(instance.internalSize, greaterThanOrEqualTo(0)); expect(instance.externalSize, greaterThanOrEqualTo(0)); expect(instance.shallowSize, greaterThanOrEqualTo(0)); diff --git a/runtime/observatory_2/lib/src/elements/heap_snapshot.dart b/runtime/observatory_2/lib/src/elements/heap_snapshot.dart index bfe8412921a53..4fde1305da956 100644 --- a/runtime/observatory_2/lib/src/elements/heap_snapshot.dart +++ b/runtime/observatory_2/lib/src/elements/heap_snapshot.dart @@ -1196,7 +1196,7 @@ class HeapSnapshotElement extends CustomElement implements Renderable { static Iterable _getChildrenSuccessor(nodeDynamic) { SnapshotObject node = nodeDynamic; - return node.successors.take(kMaxChildren).toList(); + return node.successors.toList(); } static Iterable _getChildrenPredecessor(nodeDynamic) { diff --git a/runtime/observatory_2/lib/src/service/object.dart b/runtime/observatory_2/lib/src/service/object.dart index 14d72c2721437..5e220dcf4fbf9 100644 --- a/runtime/observatory_2/lib/src/service/object.dart +++ b/runtime/observatory_2/lib/src/service/object.dart @@ -677,8 +677,6 @@ abstract class VM extends ServiceObjectOwner implements M.VM { String targetCPU; String embedder; int architectureBits; - bool assertsEnabled = false; - bool typeChecksEnabled = false; int nativeZoneMemoryUsage = 0; int pid = 0; int mallocUsed = 0; @@ -1053,8 +1051,6 @@ abstract class VM extends ServiceObjectOwner implements M.VM { maxRSS = map['_maxRSS']; currentRSS = map['_currentRSS']; profileVM = map['_profilerMode'] == 'VM'; - assertsEnabled = map['_assertsEnabled']; - typeChecksEnabled = map['_typeChecksEnabled']; _removeDeadIsolates([ ...map['isolates'], ...map['systemIsolates'], diff --git a/runtime/observatory_2/tests/service_2/object_graph_isolate_group_test.dart b/runtime/observatory_2/tests/service_2/object_graph_isolate_group_test.dart new file mode 100644 index 0000000000000..b9ddf82e05154 --- /dev/null +++ b/runtime/observatory_2/tests/service_2/object_graph_isolate_group_test.dart @@ -0,0 +1,72 @@ +// Copyright (c) 2021, 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. + +// VMOptions=--enable_isolate_groups + +// @dart = 2.7 + +import 'dart:isolate' as isolate; +import 'package:observatory_2/object_graph.dart'; +import 'package:observatory_2/service_io.dart'; +import 'package:test/test.dart'; +import 'test_helper.dart'; + +// Make sure these fields are not removed by the tree shaker. +@pragma("vm:entry-point") +dynamic bigGlobal; + +child(message) { + var bigString = message[0] as String; + var replyPort = message[1] as isolate.SendPort; + bigGlobal = bigString; + replyPort.send(null); + new isolate.RawReceivePort(); // Keep child alive. +} + +void script() { + var bigString = "x" * (1 << 20); + var port; + for (var i = 0; i < 2; i++) { + port = new isolate.RawReceivePort((_) => port.close()); + isolate.Isolate.spawn(child, [bigString, port.sendPort]); + } + bigGlobal = bigString; + print("Ready"); +} + +var tests = [ + (Isolate isolate) async { + var graph = await isolate.fetchHeapSnapshot().done; + + // We are assuming the big string is the largest in the heap, and that it + // was shared/pass-by-pointer. + List strings = graph.objects + .where((SnapshotObject obj) => obj.klass.name == "_OneByteString") + .toList(); + strings.sort((u, v) => v.shallowSize - u.shallowSize); + SnapshotObject bigString = strings[0]; + print("bigString: $bigString"); + expect(bigString.shallowSize, greaterThanOrEqualTo(1 << 20)); + + int matchingPredecessors = 0; + for (SnapshotObject predecessor in bigString.predecessors) { + print("predecessor $predecessor ${predecessor.label}"); + if (predecessor.label.contains("bigGlobal") && + predecessor.klass.name.contains("Isolate")) { + matchingPredecessors++; + } + } + + for (SnapshotObject object in graph.objects) { + if (object.klass.name.contains("Isolate")) { + print("$object / ${object.description}"); + } + } + + // Parent and two children. Seeing all 3 means we visited all the field tables. + expect(matchingPredecessors, equals(3)); + } +]; + +main(args) => runIsolateTests(args, tests, testeeBefore: script); diff --git a/runtime/observatory_2/tests/service_2/object_graph_vm_test.dart b/runtime/observatory_2/tests/service_2/object_graph_vm_test.dart index d1cb655cf8c4b..81be62eaea140 100644 --- a/runtime/observatory_2/tests/service_2/object_graph_vm_test.dart +++ b/runtime/observatory_2/tests/service_2/object_graph_vm_test.dart @@ -90,8 +90,8 @@ var tests = [ int internalSum = 0; int externalSum = 0; for (SnapshotObject instance in klass.instances) { - if (instance == graph.root) { - // The root may have 0 self size. + if (instance == graph.root || instance.klass.name.contains("Isolate")) { + // The root and fake root subdivisions have 0 self size. expect(instance.internalSize, greaterThanOrEqualTo(0)); expect(instance.externalSize, greaterThanOrEqualTo(0)); expect(instance.shallowSize, greaterThanOrEqualTo(0)); @@ -122,8 +122,8 @@ var tests = [ int internalSum = 0; int externalSum = 0; for (SnapshotObject instance in graph.objects) { - if (instance == graph.root) { - // The root may have 0 self size. + if (instance == graph.root || instance.klass.name.contains("Isolate")) { + // The root and fake root subdivisions have 0 self size. expect(instance.internalSize, greaterThanOrEqualTo(0)); expect(instance.externalSize, greaterThanOrEqualTo(0)); expect(instance.shallowSize, greaterThanOrEqualTo(0)); diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc index 35236ef6a6c7f..b1967f0cac29b 100644 --- a/runtime/vm/isolate.cc +++ b/runtime/vm/isolate.cc @@ -2628,16 +2628,18 @@ void Isolate::VisitObjectPointers(ObjectPointerVisitor* visitor, ValidationPolicy validate_frames) { ASSERT(visitor != nullptr); - // Visit objects in the isolate object store. - if (isolate_object_store() != nullptr) { - isolate_object_store()->VisitObjectPointers(visitor); - } - // Visit objects in the field table. + // N.B.: The heap snapshot writer requires visiting the field table first, so + // that the pointer visitation order aligns with order of field name metadata. if (!visitor->trace_values_through_fields()) { field_table()->VisitObjectPointers(visitor); } + // Visit objects in the isolate object store. + if (isolate_object_store() != nullptr) { + isolate_object_store()->VisitObjectPointers(visitor); + } + visitor->clear_gc_root_type(); // Visit the objects directly referenced from the isolate structure. visitor->VisitPointer(reinterpret_cast(¤t_tag_)); @@ -2810,14 +2812,19 @@ void IsolateGroup::RunWithStoppedMutatorsCallable( void IsolateGroup::VisitObjectPointers(ObjectPointerVisitor* visitor, ValidationPolicy validate_frames) { + VisitSharedPointers(visitor); + for (Isolate* isolate : isolates_) { + isolate->VisitObjectPointers(visitor, validate_frames); + } + VisitStackPointers(visitor, validate_frames); +} + +void IsolateGroup::VisitSharedPointers(ObjectPointerVisitor* visitor) { // if class table is shared, it's stored on isolate group if (class_table() != nullptr) { // Visit objects in the class table. class_table()->VisitObjectPointers(visitor); } - for (Isolate* isolate : isolates_) { - isolate->VisitObjectPointers(visitor, validate_frames); - } api_state()->VisitObjectPointersUnlocked(visitor); // Visit objects in the object store. if (object_store() != nullptr) { @@ -2825,7 +2832,6 @@ void IsolateGroup::VisitObjectPointers(ObjectPointerVisitor* visitor, } visitor->VisitPointer(reinterpret_cast(&saved_unlinked_calls_)); initial_field_table()->VisitObjectPointers(visitor); - VisitStackPointers(visitor, validate_frames); // Visit the boxed_field_list_. // 'boxed_field_list_' access via mutator and background compilation threads diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h index d7c8e5d407897..8b4f88b1ed7a5 100644 --- a/runtime/vm/isolate.h +++ b/runtime/vm/isolate.h @@ -713,6 +713,7 @@ class IsolateGroup : public IntrusiveDListEntry { // running, and the visitor must not allocate. void VisitObjectPointers(ObjectPointerVisitor* visitor, ValidationPolicy validate_frames); + void VisitSharedPointers(ObjectPointerVisitor* visitor); void VisitStackPointers(ObjectPointerVisitor* visitor, ValidationPolicy validate_frames); void VisitObjectIdRingPointers(ObjectPointerVisitor* visitor); diff --git a/runtime/vm/object_graph.cc b/runtime/vm/object_graph.cc index 40e55a83f8cad..749c0f3836a15 100644 --- a/runtime/vm/object_graph.cc +++ b/runtime/vm/object_graph.cc @@ -812,8 +812,6 @@ class Pass1Visitor : public ObjectVisitor, HandleVisitor(Thread::Current()), writer_(writer) {} - virtual bool trace_values_through_fields() const { return true; } - void VisitObject(ObjectPtr obj) { if (obj->IsPseudoObject()) return; @@ -865,6 +863,13 @@ enum NonReferenceDataTags { static const intptr_t kMaxStringElements = 128; +enum ExtraCids { + kRootExtraCid = 1, // 1-origin + kIsolateExtraCid = 2, + + kNumExtraCids = 2, +}; + class Pass2Visitor : public ObjectVisitor, public ObjectPointerVisitor, public HandleVisitor { @@ -876,13 +881,11 @@ class Pass2Visitor : public ObjectVisitor, isolate_group_(thread()->isolate_group()), writer_(writer) {} - virtual bool trace_values_through_fields() const { return true; } - void VisitObject(ObjectPtr obj) { if (obj->IsPseudoObject()) return; intptr_t cid = obj->GetClassId(); - writer_->WriteUnsigned(cid); + writer_->WriteUnsigned(cid + kNumExtraCids); writer_->WriteUnsigned(discount_sizes_ ? 0 : obj->untag()->HeapSize()); if (cid == kNullCid) { @@ -891,6 +894,16 @@ class Pass2Visitor : public ObjectVisitor, writer_->WriteUnsigned(kBoolData); writer_->WriteUnsigned( static_cast(static_cast(obj)->untag()->value_)); + } else if (cid == kSentinelCid) { + if (obj == Object::sentinel().ptr()) { + writer_->WriteUnsigned(kNameData); + writer_->WriteUtf8("uninitialized"); + } else if (obj == Object::transition_sentinel().ptr()) { + writer_->WriteUnsigned(kNameData); + writer_->WriteUtf8("initializing"); + } else { + writer_->WriteUnsigned(kNoData); + } } else if (cid == kSmiCid) { UNREACHABLE(); } else if (cid == kMintCid) { @@ -1073,6 +1086,16 @@ class Pass2Visitor : public ObjectVisitor, } } + void CountExtraRefs(intptr_t count) { + ASSERT(!writing_); + counted_ += count; + } + void WriteExtraRef(intptr_t oid) { + ASSERT(writing_); + written_++; + writer_->WriteUnsigned(oid); + } + private: IsolateGroup* isolate_group_; HeapSnapshotWriter* const writer_; @@ -1105,6 +1128,36 @@ class Pass3Visitor : public ObjectVisitor { DISALLOW_COPY_AND_ASSIGN(Pass3Visitor); }; +class CollectStaticFieldNames : public ObjectVisitor { + public: + CollectStaticFieldNames(intptr_t field_table_size, + const char** field_table_names) + : ObjectVisitor(), + field_table_size_(field_table_size), + field_table_names_(field_table_names), + field_(Field::Handle()) {} + + void VisitObject(ObjectPtr obj) { + if (obj->IsField()) { + field_ ^= obj; + if (field_.is_static()) { + intptr_t id = field_.field_id(); + if (id > 0) { + ASSERT(id < field_table_size_); + field_table_names_[id] = field_.UserVisibleNameCString(); + } + } + } + } + + private: + intptr_t field_table_size_; + const char** field_table_names_; + Field& field_; + + DISALLOW_COPY_AND_ASSIGN(CollectStaticFieldNames); +}; + void HeapSnapshotWriter::Write() { HeapIterationScope iteration(thread()); @@ -1134,7 +1187,46 @@ void HeapSnapshotWriter::Write() { Array& fields = Array::Handle(); Field& field = Field::Handle(); - WriteUnsigned(class_count_); + intptr_t field_table_size = isolate()->field_table()->NumFieldIds(); + const char** field_table_names = + thread()->zone()->Alloc(field_table_size); + for (intptr_t i = 0; i < field_table_size; i++) { + field_table_names[i] = nullptr; + } + { + CollectStaticFieldNames visitor(field_table_size, field_table_names); + iteration.IterateObjects(&visitor); + } + + WriteUnsigned(class_count_ + kNumExtraCids); + { + ASSERT(kRootExtraCid == 1); + WriteUnsigned(0); // Flags + WriteUtf8("Root"); // Name + WriteUtf8(""); // Library name + WriteUtf8(""); // Library uri + WriteUtf8(""); // Reserved + WriteUnsigned(0); // Field count + } + { + ASSERT(kIsolateExtraCid == 2); + WriteUnsigned(0); // Flags + WriteUtf8("Isolate"); // Name + WriteUtf8(""); // Library name + WriteUtf8(""); // Library uri + WriteUtf8(""); // Reserved + + WriteUnsigned(field_table_size); // Field count + for (intptr_t i = 0; i < field_table_size; i++) { + intptr_t flags = 1; // Strong. + WriteUnsigned(flags); + WriteUnsigned(i); // Index. + const char* name = field_table_names[i]; + WriteUtf8(name == nullptr ? "" : name); + WriteUtf8(""); // Reserved + } + } + ASSERT(kNumExtraCids == 2); for (intptr_t cid = 1; cid <= class_count_; cid++) { if (!class_table->HasValidClassAt(cid)) { WriteUnsigned(0); // Flags @@ -1227,13 +1319,22 @@ void HeapSnapshotWriter::Write() { SetupCountingPages(); + intptr_t num_isolates = 0; { Pass1Visitor visitor(this); - // Root "object". + // Root "objects". ++object_count_; - isolate()->VisitObjectPointers(&visitor, - ValidationPolicy::kDontValidateFrames); + isolate_group()->VisitSharedPointers(&visitor); + isolate_group()->ForEachIsolate( + [&](Isolate* isolate) { + ++object_count_; + isolate->VisitObjectPointers(&visitor, + ValidationPolicy::kDontValidateFrames); + ++num_isolates; + }, + /*at_safepoint=*/true); + CountReferences(num_isolates); // Heap objects. iteration.IterateVMIsolateObjects(&visitor); @@ -1249,16 +1350,35 @@ void HeapSnapshotWriter::Write() { WriteUnsigned(reference_count_); WriteUnsigned(object_count_); - // Root "object". - WriteUnsigned(0); // cid - WriteUnsigned(0); // shallowSize - WriteUnsigned(kNoData); - visitor.DoCount(); - isolate()->VisitObjectPointers(&visitor, - ValidationPolicy::kDontValidateFrames); - visitor.DoWrite(); - isolate()->VisitObjectPointers(&visitor, - ValidationPolicy::kDontValidateFrames); + // Root "objects". + { + WriteUnsigned(kRootExtraCid); + WriteUnsigned(0); // shallowSize + WriteUnsigned(kNoData); + visitor.DoCount(); + isolate_group()->VisitSharedPointers(&visitor); + visitor.CountExtraRefs(num_isolates); + visitor.DoWrite(); + isolate_group()->VisitSharedPointers(&visitor); + for (intptr_t i = 0; i < num_isolates; i++) { + visitor.WriteExtraRef(i + 2); // 0 = sentinel, 1 = root, 2+ = isolates + } + } + isolate_group()->ForEachIsolate( + [&](Isolate* isolate) { + WriteUnsigned(kIsolateExtraCid); + WriteUnsigned(0); // shallowSize + WriteUnsigned(kNameData); + WriteUtf8( + OS::SCreate(thread()->zone(), "%" Pd64, isolate->main_port())); + visitor.DoCount(); + isolate->VisitObjectPointers(&visitor, + ValidationPolicy::kDontValidateFrames); + visitor.DoWrite(); + isolate->VisitObjectPointers(&visitor, + ValidationPolicy::kDontValidateFrames); + }, + /*at_safepoint=*/true); // Heap objects. visitor.set_discount_sizes(true); @@ -1277,6 +1397,8 @@ void HeapSnapshotWriter::Write() { // Handle root object. WriteUnsigned(0); + isolate_group()->ForEachIsolate([&](Isolate* isolate) { WriteUnsigned(0); }, + /*at_safepoint=*/true); // Handle visit rest of the objects. iteration.IterateVMIsolateObjects(&visitor); diff --git a/runtime/vm/raw_object_fields.cc b/runtime/vm/raw_object_fields.cc index 0c64af6e50b35..186f5107a5ab4 100644 --- a/runtime/vm/raw_object_fields.cc +++ b/runtime/vm/raw_object_fields.cc @@ -156,15 +156,23 @@ namespace dart { F(String, length_) \ F(Array, type_arguments_) \ F(Array, length_) \ + F(ImmutableArray, type_arguments_) \ + F(ImmutableArray, length_) \ F(GrowableObjectArray, type_arguments_) \ F(GrowableObjectArray, length_) \ F(GrowableObjectArray, data_) \ - F(LinkedHashBase, type_arguments_) \ - F(LinkedHashBase, index_) \ - F(LinkedHashBase, hash_mask_) \ - F(LinkedHashBase, data_) \ - F(LinkedHashBase, used_data_) \ - F(LinkedHashBase, deleted_keys_) \ + F(LinkedHashMap, type_arguments_) \ + F(LinkedHashMap, index_) \ + F(LinkedHashMap, hash_mask_) \ + F(LinkedHashMap, data_) \ + F(LinkedHashMap, used_data_) \ + F(LinkedHashSet, deleted_keys_) \ + F(LinkedHashSet, type_arguments_) \ + F(LinkedHashSet, index_) \ + F(LinkedHashSet, hash_mask_) \ + F(LinkedHashSet, data_) \ + F(LinkedHashSet, used_data_) \ + F(LinkedHashSet, deleted_keys_) \ F(TypedData, length_) \ F(ExternalTypedData, length_) \ F(ReceivePort, send_port_) \ From a899525bdc6aa1c130cc2e2b2f5e59ef25350559 Mon Sep 17 00:00:00 2001 From: Nicholas Shahan Date: Tue, 17 Aug 2021 17:32:52 +0000 Subject: [PATCH 4/5] [tests] Add regression tests Issue: https://github.com/dart-lang/sdk/issues/46867 Change-Id: I7e2912752ac8c48df233f27869599280e2c322fa Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/209853 Reviewed-by: Mark Zhou Commit-Queue: Nicholas Shahan --- tests/language/regress/regress46867_test.dart | 32 +++++++++++++++++ .../language_2/regress/regress46867_test.dart | 34 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 tests/language/regress/regress46867_test.dart create mode 100644 tests/language_2/regress/regress46867_test.dart diff --git a/tests/language/regress/regress46867_test.dart b/tests/language/regress/regress46867_test.dart new file mode 100644 index 0000000000000..7999cc849a61a --- /dev/null +++ b/tests/language/regress/regress46867_test.dart @@ -0,0 +1,32 @@ +// Copyright (c) 2021, 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. + +// Regression test for https://github.com/dart-lang/sdk/issues/46867 + +import 'package:expect/expect.dart'; + +class Interface { + covariant Object x = 'from Interface'; +} + +mixin Mixin implements Interface {} + +class BaseClass { + static var getterCallCount = 0; + static var setterCallCount = 0; + Object get x => getterCallCount++; + set x(Object value) => setterCallCount++; +} + +class SubClass extends BaseClass with Mixin {} + +void main() { + Expect.equals(0, BaseClass.getterCallCount); + SubClass().x; + Expect.equals(1, BaseClass.getterCallCount); + + Expect.equals(0, BaseClass.setterCallCount); + SubClass().x = 42; + Expect.equals(1, BaseClass.setterCallCount); +} diff --git a/tests/language_2/regress/regress46867_test.dart b/tests/language_2/regress/regress46867_test.dart new file mode 100644 index 0000000000000..750b2234243c8 --- /dev/null +++ b/tests/language_2/regress/regress46867_test.dart @@ -0,0 +1,34 @@ +// Copyright (c) 2021, 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. + +// @dart = 2.9 + +// Regression test for https://github.com/dart-lang/sdk/issues/46867 + +import 'package:expect/expect.dart'; + +class Interface { + covariant Object x = 'from Interface'; +} + +mixin Mixin implements Interface {} + +class BaseClass { + static var getterCallCount = 0; + static var setterCallCount = 0; + Object get x => getterCallCount++; + set x(Object value) => setterCallCount++; +} + +class SubClass extends BaseClass with Mixin {} + +void main() { + Expect.equals(0, BaseClass.getterCallCount); + SubClass().x; + Expect.equals(1, BaseClass.getterCallCount); + + Expect.equals(0, BaseClass.setterCallCount); + SubClass().x = 42; + Expect.equals(1, BaseClass.setterCallCount); +} From 5b3cadc7e6d7be94ef959e3733357980ff69c684 Mon Sep 17 00:00:00 2001 From: Nicholas Shahan Date: Tue, 17 Aug 2021 18:26:02 +0000 Subject: [PATCH 5/5] [ddc] Add getter to accompany setter from mixin When a mixin introduces a forwarding stub setter in the class hierarchy there also needs to be the accompanying getter in the same class if a getter is present further up the class hierarchy. This is because the setter is represented as a property with only a setter and the lack of a getter will not cause a lookup further up the prototype chain. Fixes: https://github.com/dart-lang/sdk/issues/46867 Change-Id: I8e41eb9d2569f0819200a82367ab7c723a1011cd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/209854 Commit-Queue: Nicholas Shahan Reviewed-by: Mark Zhou --- pkg/dev_compiler/lib/src/kernel/compiler.dart | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart index 57a1c38a2cda1..4f3d3c5a6a04b 100644 --- a/pkg/dev_compiler/lib/src/kernel/compiler.dart +++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart @@ -1127,13 +1127,29 @@ class ProgramCompiler extends ComputeOnceConstantVisitor var mixinName = getLocalClassName(superclass) + '_' + getLocalClassName(mixinClass); var mixinId = _emitTemporaryId(mixinName + '\$'); - // Collect all forwarding stubs from anonymous mixins classes. These will - // contain covariant parameter checks that need to be applied. - var forwardingMethodStubs = [ + // Collect all forwarding stub setters from anonymous mixins classes. + // These will contain covariant parameter checks that need to be applied. + var savedClassProperties = _classProperties; + _classProperties = + ClassPropertyModel.build(_types, _extensionTypes, _virtualFields, m); + + var forwardingSetters = { for (var procedure in m.procedures) if (procedure.isForwardingStub && !procedure.isAbstract) - _emitMethodDeclaration(procedure) - ]; + procedure.name.text: procedure + }; + + var forwardingMethodStubs = []; + for (var s in forwardingSetters.values) { + forwardingMethodStubs.add(_emitMethodDeclaration(s)); + // If there are getters matching the setters somewhere above in the + // class hierarchy we must also generate a forwarding getter due to the + // representation used in the compiled JavaScript. + var getterWrapper = _emitSuperAccessorWrapper(s, {}, forwardingSetters); + if (getterWrapper != null) forwardingMethodStubs.add(getterWrapper); + } + + _classProperties = savedClassProperties; // Bind the mixin class to a name to workaround a V8 bug with es6 classes // and anonymous function names.