Version 2.13.0-33.0.dev
Merge commit '33910fe2b875d8d9e48f86bf63b11c4cadb59894' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
index ddd82cc..1ee1ff8 100644
--- a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
@@ -393,24 +393,6 @@
'ExpressionInfo(after: $after, _ifTrue: $ifTrue, ifFalse: $ifFalse)';
}
-/// Non-promotion reason describing the situation where an expression was not
-/// promoted due to the fact that it's a field (technically, a property get).
-class FieldNotPromoted extends NonPromotionReason {
- /// The name of the property.
- final String propertyName;
-
- FieldNotPromoted(this.propertyName);
-
- @override
- String get shortName => 'fieldNotPromoted($propertyName)';
-
- @override
- R accept<R, Node extends Object, Expression extends Object,
- Variable extends Object>(
- NonPromotionReasonVisitor<R, Node, Expression, Variable> visitor) =>
- visitor.visitFieldNotPromoted(this);
-}
-
/// Implementation of flow analysis to be shared between the analyzer and the
/// front end.
///
@@ -2405,7 +2387,25 @@
R visitDemoteViaForEachVariableWrite(
DemoteViaForEachVariableWrite<Variable, Node> reason);
- R visitFieldNotPromoted(FieldNotPromoted reason);
+ R visitPropertyNotPromoted(PropertyNotPromoted reason);
+}
+
+/// Non-promotion reason describing the situation where an expression was not
+/// promoted due to the fact that it's a property get.
+class PropertyNotPromoted extends NonPromotionReason {
+ /// The name of the property.
+ final String propertyName;
+
+ PropertyNotPromoted(this.propertyName);
+
+ @override
+ String get shortName => 'propertyNotPromoted';
+
+ @override
+ R accept<R, Node extends Object, Expression extends Object,
+ Variable extends Object>(
+ NonPromotionReasonVisitor<R, Node, Expression, Variable> visitor) =>
+ visitor.visitPropertyNotPromoted(this);
}
/// Immutable data structure modeling the reachability of the given point in the
@@ -5000,7 +5000,7 @@
List<Type>? promotedTypes = _getInfo(variableInfo)?.promotedTypes;
if (promotedTypes != null) {
for (Type type in promotedTypes) {
- result[type] = new FieldNotPromoted(propertyName);
+ result[type] = new PropertyNotPromoted(propertyName);
}
}
return result;
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
index 1657689..a344d9e 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
@@ -5638,7 +5638,7 @@
]);
});
- group('because field', () {
+ group('because property', () {
test('via explicit this', () {
var h = Harness();
h.run([
@@ -5648,7 +5648,7 @@
this_('C').propertyGet('field').whyNotPromoted((reasons) {
expect(reasons.keys, unorderedEquals([Type('Object')]));
var nonPromotionReason = reasons.values.single;
- expect(nonPromotionReason, TypeMatcher<FieldNotPromoted>());
+ expect(nonPromotionReason, TypeMatcher<PropertyNotPromoted>());
}).stmt,
]);
});
@@ -5662,7 +5662,7 @@
thisOrSuperPropertyGet('field').whyNotPromoted((reasons) {
expect(reasons.keys, unorderedEquals([Type('Object')]));
var nonPromotionReason = reasons.values.single;
- expect(nonPromotionReason, TypeMatcher<FieldNotPromoted>());
+ expect(nonPromotionReason, TypeMatcher<PropertyNotPromoted>());
}).stmt,
]);
});
@@ -5678,7 +5678,7 @@
x.read.propertyGet('field').whyNotPromoted((reasons) {
expect(reasons.keys, unorderedEquals([Type('Object')]));
var nonPromotionReason = reasons.values.single;
- expect(nonPromotionReason, TypeMatcher<FieldNotPromoted>());
+ expect(nonPromotionReason, TypeMatcher<PropertyNotPromoted>());
}).stmt,
]);
});
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/extension_property.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/extension_property.dart
new file mode 100644
index 0000000..dbbefe9
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/extension_property.dart
@@ -0,0 +1,47 @@
+// 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.
+
+class C {
+ get_property_via_explicit_this() {
+ if (this.i == null) return;
+ this.i. /*notPromoted(propertyNotPromoted(member:E|get#i))*/ isEven;
+ }
+
+ get_property_via_explicit_this_parenthesized() {
+ if ((this).i == null) return;
+ (this).i. /*notPromoted(propertyNotPromoted(member:E|get#i))*/ isEven;
+ }
+
+ get_property_by_implicit_this() {
+ if (i == null) return;
+ i. /*notPromoted(propertyNotPromoted(member:E|get#i))*/ isEven;
+ }
+}
+
+extension E on C {
+ int? get i => null;
+ int? get j => null;
+}
+
+class D extends C {
+ get_property_by_implicit_super() {
+ if (i == null) return;
+ i. /*notPromoted(propertyNotPromoted(member:E|get#i))*/ isEven;
+ }
+}
+
+get_property_via_prefixed_identifier(C c) {
+ if (c.i == null) return;
+ c.i. /*notPromoted(propertyNotPromoted(member:E|get#i))*/ isEven;
+}
+
+get_property_via_prefixed_identifier_mismatched_target(C c1, C c2) {
+ if (c1.i == null) return;
+ c2.i.isEven;
+}
+
+get_property_via_prefixed_identifier_mismatched_property(C c) {
+ if (c.i == null) return;
+ c.j.isEven;
+}
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/field.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/field.dart
index 0bbec4e..1829ad0 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/field.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/field.dart
@@ -8,35 +8,35 @@
get_field_via_explicit_this() {
if (this.i == null) return;
- this.i. /*notPromoted(fieldNotPromoted(i))*/ isEven;
+ this.i. /*notPromoted(propertyNotPromoted(member:C.i))*/ isEven;
}
get_field_via_explicit_this_parenthesized() {
if ((this).i == null) return;
- (this).i. /*notPromoted(fieldNotPromoted(i))*/ isEven;
+ (this).i. /*notPromoted(propertyNotPromoted(member:C.i))*/ isEven;
}
get_field_by_implicit_this() {
if (i == null) return;
- i. /*notPromoted(fieldNotPromoted(i))*/ isEven;
+ i. /*notPromoted(propertyNotPromoted(member:C.i))*/ isEven;
}
}
class D extends C {
get_field_via_explicit_super() {
if (super.i == null) return;
- super.i. /*notPromoted(fieldNotPromoted(i))*/ isEven;
+ super.i. /*notPromoted(propertyNotPromoted(member:C.i))*/ isEven;
}
get_field_by_implicit_super() {
if (i == null) return;
- i. /*notPromoted(fieldNotPromoted(i))*/ isEven;
+ i. /*notPromoted(propertyNotPromoted(member:C.i))*/ isEven;
}
}
get_field_via_prefixed_identifier(C c) {
if (c.i == null) return;
- c.i. /*notPromoted(fieldNotPromoted(i))*/ isEven;
+ c.i. /*notPromoted(propertyNotPromoted(member:C.i))*/ isEven;
}
get_field_via_prefixed_identifier_mismatched_target(C c1, C c2) {
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/property.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/property.dart
new file mode 100644
index 0000000..24b85cc
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/property.dart
@@ -0,0 +1,50 @@
+// 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.
+
+class C {
+ int? get i => null;
+ int? get j => null;
+
+ get_property_via_explicit_this() {
+ if (this.i == null) return;
+ this.i. /*notPromoted(propertyNotPromoted(member:C.i))*/ isEven;
+ }
+
+ get_property_via_explicit_this_parenthesized() {
+ if ((this).i == null) return;
+ (this).i. /*notPromoted(propertyNotPromoted(member:C.i))*/ isEven;
+ }
+
+ get_property_by_implicit_this() {
+ if (i == null) return;
+ i. /*notPromoted(propertyNotPromoted(member:C.i))*/ isEven;
+ }
+}
+
+class D extends C {
+ get_property_via_explicit_super() {
+ if (super.i == null) return;
+ super.i. /*notPromoted(propertyNotPromoted(member:C.i))*/ isEven;
+ }
+
+ get_property_by_implicit_super() {
+ if (i == null) return;
+ i. /*notPromoted(propertyNotPromoted(member:C.i))*/ isEven;
+ }
+}
+
+get_property_via_prefixed_identifier(C c) {
+ if (c.i == null) return;
+ c.i. /*notPromoted(propertyNotPromoted(member:C.i))*/ isEven;
+}
+
+get_property_via_prefixed_identifier_mismatched_target(C c1, C c2) {
+ if (c1.i == null) return;
+ c2.i.isEven;
+}
+
+get_property_via_prefixed_identifier_mismatched_property(C c) {
+ if (c.i == null) return;
+ c.j.isEven;
+}
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart b/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart
index b6aaae5..56d2102 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart
@@ -81,6 +81,7 @@
double inheritanceDistance = 0.0,
double isConstant = 0.0,
double isNoSuchMethod = 0.0,
+ double keyword = 0.0,
double localVariableDistance = 0.0,
double startsWithDollar = 0.0,
double superMatches = 0.0}) {
@@ -89,6 +90,8 @@
assert(hasDeprecated.between(-1.0, 0.0));
assert(inheritanceDistance.between(0.0, 1.0));
assert(isConstant.between(0.0, 1.0));
+ assert(isNoSuchMethod.between(-1.0, 0.0));
+ assert(keyword.between(0.0, 1.0));
assert(localVariableDistance.between(0.0, 1.0));
assert(startsWithDollar.between(-1.0, 0.0));
assert(superMatches.between(0.0, 1.0));
@@ -99,6 +102,7 @@
inheritanceDistance,
isConstant,
isNoSuchMethod,
+ keyword,
localVariableDistance,
startsWithDollar,
superMatches,
@@ -109,6 +113,7 @@
1.00, // inheritanceDistance
1.00, // isConstant
1.00, // isNoSuchMethod
+ 1.00, // keyword
1.00, // localVariableDistance
0.50, // startsWithDollar
1.00, // superMatches
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
index 47d35a8b..3164f0c 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
@@ -496,11 +496,21 @@
/// Add a suggestion for a [keyword]. The [offset] is the offset from the
/// beginning of the keyword where the cursor will be left.
void suggestKeyword(String keyword, {int offset}) {
+ DartType elementType;
+ if (keyword == 'null') {
+ elementType = request.featureComputer.typeProvider.nullType;
+ } else if (keyword == 'false' || keyword == 'true') {
+ elementType = request.featureComputer.typeProvider.boolType;
+ }
+ var contextType = request.featureComputer
+ .contextTypeFeature(request.contextType, elementType);
var keywordFeature = request.featureComputer
.keywordFeature(keyword, request.opType.completionLocation);
- // TODO(brianwilkerson) The default value should probably be a constant.
- var relevance = toRelevance(keywordFeature);
- listener?.computedFeatures(keyword: keywordFeature);
+ var score =
+ weightedAverage(contextType: contextType, keyword: keywordFeature);
+ var relevance = toRelevance(score);
+ listener?.computedFeatures(
+ contextType: contextType, keyword: keywordFeature);
_add(CompletionSuggestion(CompletionSuggestionKind.KEYWORD, relevance,
keyword, offset ?? keyword.length, 0, false, false));
}
diff --git a/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart
index 15898fa..65bef9a 100644
--- a/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart
@@ -19,6 +19,7 @@
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/util/ast_data_extractor.dart';
/// Helper for resolving properties (getters, setters, or methods).
class TypePropertyResolver {
@@ -119,24 +120,32 @@
}
}
- var whyNotPromoted = receiver == null
- ? null
- : _resolver.flowAnalysis?.flow?.whyNotPromoted(receiver);
List<DiagnosticMessage> messages = [];
- if (whyNotPromoted != null) {
- for (var entry in whyNotPromoted.entries) {
- var whyNotPromotedVisitor = _WhyNotPromotedVisitor(
- _resolver.source, _resolver.flowAnalysis!.dataForTesting);
- if (_typeSystem.isPotentiallyNullable(entry.key)) continue;
- if (_resolver.flowAnalysis!.dataForTesting != null) {
- _resolver.flowAnalysis!.dataForTesting!
- .nonPromotionReasons[nameErrorEntity] = entry.value.shortName;
+ if (receiver != null) {
+ var whyNotPromoted =
+ _resolver.flowAnalysis?.flow?.whyNotPromoted(receiver);
+ if (whyNotPromoted != null) {
+ for (var entry in whyNotPromoted.entries) {
+ var whyNotPromotedVisitor = _WhyNotPromotedVisitor(_resolver.source,
+ receiver, _resolver.flowAnalysis!.dataForTesting);
+ if (_typeSystem.isPotentiallyNullable(entry.key)) continue;
+ var message = entry.value.accept(whyNotPromotedVisitor);
+ if (message != null) {
+ if (_resolver.flowAnalysis!.dataForTesting != null) {
+ var nonPromotionReasonText = entry.value.shortName;
+ if (whyNotPromotedVisitor.propertyReference != null) {
+ var id =
+ computeMemberId(whyNotPromotedVisitor.propertyReference!);
+ nonPromotionReasonText += '($id)';
+ }
+ _resolver.flowAnalysis!.dataForTesting!
+ .nonPromotionReasons[nameErrorEntity] =
+ nonPromotionReasonText;
+ }
+ messages = [message];
+ }
+ break;
}
- var message = entry.value.accept(whyNotPromotedVisitor);
- if (message != null) {
- messages = [message];
- }
- break;
}
}
@@ -297,9 +306,13 @@
PromotableElement> {
final Source source;
+ final Expression _receiver;
+
final FlowAnalysisDataForTesting? _dataForTesting;
- _WhyNotPromotedVisitor(this.source, this._dataForTesting);
+ PropertyAccessorElement? propertyReference;
+
+ _WhyNotPromotedVisitor(this.source, this._receiver, this._dataForTesting);
@override
DiagnosticMessage? visitDemoteViaExplicitWrite(
@@ -343,9 +356,36 @@
}
@override
- DiagnosticMessage? visitFieldNotPromoted(FieldNotPromoted reason) {
- // TODO(paulberry): how to report this?
- return null;
+ DiagnosticMessage? visitPropertyNotPromoted(PropertyNotPromoted reason) {
+ var receiver = _receiver;
+ Element? receiverElement;
+ if (receiver is SimpleIdentifier) {
+ receiverElement = receiver.staticElement;
+ } else if (receiver is PropertyAccess) {
+ receiverElement = receiver.propertyName.staticElement;
+ } else if (receiver is PrefixedIdentifier) {
+ receiverElement = receiver.identifier.staticElement;
+ } else {
+ assert(false, 'Unrecognized receiver: ${receiver.runtimeType}');
+ }
+ if (receiverElement is PropertyAccessorElement) {
+ propertyReference = receiverElement;
+ return _contextMessageForProperty(receiverElement, reason.propertyName);
+ } else {
+ assert(receiverElement == null,
+ 'Unrecognized receiver element: ${receiverElement.runtimeType}');
+ return null;
+ }
+ }
+
+ DiagnosticMessageImpl _contextMessageForProperty(
+ PropertyAccessorElement property, String propertyName) {
+ return DiagnosticMessageImpl(
+ filePath: property.source.fullName,
+ message:
+ "'$propertyName' refers to a property so it could not be promoted.",
+ offset: property.nameOffset,
+ length: property.nameLength);
}
DiagnosticMessageImpl _contextMessageForWrite(
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart
index a38bd45..511bacd 100644
--- a/pkg/analyzer/lib/src/generated/error_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -4623,6 +4623,7 @@
"&" == name ||
"<<" == name ||
">>" == name ||
+ ">>>" == name ||
"[]" == name) {
expected = 1;
} else if ("~" == name) {
diff --git a/pkg/analyzer/lib/src/util/ast_data_extractor.dart b/pkg/analyzer/lib/src/util/ast_data_extractor.dart
index fe220d5..4ae6f75 100644
--- a/pkg/analyzer/lib/src/util/ast_data_extractor.dart
+++ b/pkg/analyzer/lib/src/util/ast_data_extractor.dart
@@ -7,6 +7,30 @@
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
+MemberId computeMemberId(Element element) {
+ var enclosingElement = element.enclosingElement;
+ if (enclosingElement is CompilationUnitElement) {
+ var memberName = element.name!;
+ if (element is PropertyAccessorElement && element.isSetter) {
+ memberName += '=';
+ }
+ return MemberId.internal(memberName);
+ } else if (enclosingElement is ClassElement) {
+ var memberName = element.name!;
+ var className = enclosingElement.name;
+ return MemberId.internal(memberName, className: className);
+ } else if (enclosingElement is ExtensionElement) {
+ var memberName = element.name!;
+ var extensionName = enclosingElement.name;
+ if (element is PropertyAccessorElement) {
+ memberName = '${element.isGetter ? 'get' : 'set'}#$memberName';
+ }
+ return MemberId.internal('$extensionName|$memberName');
+ }
+ throw UnimplementedError(
+ 'TODO(paulberry): $element (${element.runtimeType})');
+}
+
/// Abstract IR visitor for computing data corresponding to a node or element,
/// and record it with a generic [Id]
abstract class AstDataExtractor<T> extends GeneralizingAstVisitor<void>
@@ -74,27 +98,7 @@
Id createMemberId(Declaration node) {
var element = node.declaredElement!;
- var enclosingElement = element.enclosingElement;
- if (enclosingElement is CompilationUnitElement) {
- var memberName = element.name!;
- if (element is PropertyAccessorElement && element.isSetter) {
- memberName += '=';
- }
- return MemberId.internal(memberName);
- } else if (enclosingElement is ClassElement) {
- var memberName = element.name!;
- var className = enclosingElement.name;
- return MemberId.internal(memberName, className: className);
- } else if (enclosingElement is ExtensionElement) {
- var memberName = element.name!;
- var extensionName = enclosingElement.name;
- if (element is PropertyAccessorElement) {
- memberName = '${element.isGetter ? 'get' : 'set'}#$memberName';
- }
- return MemberId.internal('$extensionName|$memberName');
- }
- throw UnimplementedError(
- 'TODO(paulberry): $element (${element.runtimeType})');
+ return computeMemberId(element);
}
NodeId createStatementId(Statement node) =>
diff --git a/pkg/analyzer/test/generated/constant_test.dart b/pkg/analyzer/test/generated/constant_test.dart
index 0114748..5b8847d 100644
--- a/pkg/analyzer/test/generated/constant_test.dart
+++ b/pkg/analyzer/test/generated/constant_test.dart
@@ -5,7 +5,6 @@
@deprecated
library analyzer.test.constant_test;
-import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/generated/constant.dart';
import 'package:test/test.dart';
@@ -21,18 +20,6 @@
@reflectiveTest
class ConstantEvaluatorTest extends PubPackageResolutionTest {
- @override
- void setUp() {
- super.setUp();
- writeTestPackageAnalysisOptionsFile(
- AnalysisOptionsFileConfig(
- experiments: [
- EnableString.triple_shift,
- ],
- ),
- );
- }
-
test_bitAnd_int_int() async {
await _assertValueInt(74 & 42, "74 & 42");
}
diff --git a/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart b/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart
index 1aba2dd..d42517f 100644
--- a/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart
+++ b/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart
@@ -263,7 +263,10 @@
super.setUp();
writeTestPackageAnalysisOptionsFile(
AnalysisOptionsFileConfig(
- experiments: [EnableString.nonfunction_type_aliases],
+ experiments: [
+ EnableString.nonfunction_type_aliases,
+ EnableString.triple_shift,
+ ],
),
);
writeTestPackageConfig(
diff --git a/pkg/analyzer/test/src/diagnostics/null_aware_before_operator_test.dart b/pkg/analyzer/test/src/diagnostics/null_aware_before_operator_test.dart
index 0dc59fb..ee6be3f 100644
--- a/pkg/analyzer/test/src/diagnostics/null_aware_before_operator_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/null_aware_before_operator_test.dart
@@ -13,11 +13,10 @@
});
}
+/// This diagnostic is only reported in pre-null safe code.
@reflectiveTest
class NullAwareBeforeOperatorTest extends PubPackageResolutionTest
with WithoutNullSafetyMixin {
- // TODO(https://github.com/dart-lang/sdk/issues/44666): Use null safety in
- // test cases.
test_assignment() async {
await assertNoErrorsInCode(r'''
m(x) {
diff --git a/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart b/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart
index 4c97b99..7ca7f8d 100644
--- a/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart
@@ -17,8 +17,7 @@
}
@reflectiveTest
-class InvalidUseOfNullValueTest extends PubPackageResolutionTest
- with WithNullSafetyMixin {
+class InvalidUseOfNullValueTest extends PubPackageResolutionTest {
test_as() async {
await assertNoErrorsInCode(r'''
m() {
@@ -124,7 +123,7 @@
@reflectiveTest
class UncheckedUseOfNullableValueInsideExtensionTest
- extends PubPackageResolutionTest with WithNullSafetyMixin {
+ extends PubPackageResolutionTest {
test_indexExpression_nonNullable() async {
await assertNoErrorsInCode(r'''
class A {
@@ -338,8 +337,7 @@
}
@reflectiveTest
-class UncheckedUseOfNullableValueTest extends PubPackageResolutionTest
- with WithNullSafetyMixin {
+class UncheckedUseOfNullableValueTest extends PubPackageResolutionTest {
test_and_nonNullable() async {
await assertNoErrorsInCode(r'''
m() {
@@ -409,7 +407,8 @@
}
''', [
error(CompileTimeErrorCode.UNCHECKED_PROPERTY_ACCESS_OF_NULLABLE_VALUE,
- 100, 3),
+ 100, 3,
+ contextMessages: [message('/home/test/lib/test.dart', 56, 1)]),
]);
assertAssignment(
@@ -550,7 +549,8 @@
}
''', [
error(CompileTimeErrorCode.UNCHECKED_PROPERTY_ACCESS_OF_NULLABLE_VALUE,
- 101, 3),
+ 101, 3,
+ contextMessages: [message('/home/test/lib/test.dart', 56, 1)]),
]);
assertAssignment(
@@ -1424,7 +1424,8 @@
}
''', [
error(CompileTimeErrorCode.UNCHECKED_PROPERTY_ACCESS_OF_NULLABLE_VALUE,
- 101, 3),
+ 101, 3,
+ contextMessages: [message('/home/test/lib/test.dart', 56, 1)]),
]);
var propertyAccess1 = findNode.propertyAccess('b.a?.x; // 1');
var propertyAccess2 = findNode.propertyAccess('b.a.x; // 2');
@@ -1493,7 +1494,8 @@
}
''', [
error(CompileTimeErrorCode.UNCHECKED_PROPERTY_ACCESS_OF_NULLABLE_VALUE,
- 142, 5),
+ 142, 5,
+ contextMessages: [message('/home/test/lib/test.dart', 56, 1)]),
]);
var propertyAccess1 = findNode.propertyAccess('x; // 1');
var propertyAccess2 = findNode.propertyAccess('x; // 2');
@@ -1530,7 +1532,8 @@
}
''', [
error(CompileTimeErrorCode.UNCHECKED_PROPERTY_ACCESS_OF_NULLABLE_VALUE,
- 148, 3),
+ 148, 3,
+ contextMessages: [message('/home/test/lib/test.dart', 101, 1)]),
]);
var propertyAccess1 = findNode.propertyAccess('x; // 1');
var propertyAccess2 = findNode.propertyAccess('x; // 2');
@@ -1608,6 +1611,19 @@
''');
}
+ test_tripleShift_nullable() async {
+ await assertErrorsInCode(r'''
+m(String? s) {
+ s?.length >>> 2;
+}
+''', [
+ error(
+ CompileTimeErrorCode.UNCHECKED_OPERATOR_INVOCATION_OF_NULLABLE_VALUE,
+ 17,
+ 9),
+ ]);
+ }
+
test_yieldEach_nonNullable() async {
await assertNoErrorsInCode(r'''
m() sync* {
diff --git a/pkg/analyzer/test/src/diagnostics/wrong_number_of_parameters_for_operator_test.dart b/pkg/analyzer/test/src/diagnostics/wrong_number_of_parameters_for_operator_test.dart
index cb48547..9b90ee6 100644
--- a/pkg/analyzer/test/src/diagnostics/wrong_number_of_parameters_for_operator_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/wrong_number_of_parameters_for_operator_test.dart
@@ -30,6 +30,7 @@
await _checkTooFewAndTooMany('&');
await _checkTooFewAndTooMany('<<');
await _checkTooFewAndTooMany('>>');
+ await _checkTooFewAndTooMany('>>>');
await _checkTooFewAndTooMany('[]');
}
@@ -48,6 +49,7 @@
await _checkCorrectSingle("&");
await _checkCorrectSingle("<<");
await _checkCorrectSingle(">>");
+ await _checkCorrectSingle(">>>");
await _checkCorrectSingle("[]");
}
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 3e1f89c..1bbce68 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -6062,7 +6062,15 @@
if (type.nullability == Nullability.legacy) {
type = type.withDeclaredNullability(Nullability.nonNullable);
}
- assert(!_isInForeignJS || type.nullability == Nullability.nonNullable);
+ assert(!_isInForeignJS ||
+ type.nullability == Nullability.nonNullable ||
+ // The types dynamic, void, and Null all instrinsicly have
+ // `Nullability.nullable` but are handled explicitly without emiting
+ // the nullable runtime wrapper. They are safe to allow through
+ // unchanged.
+ type == const DynamicType() ||
+ type == const NullType() ||
+ type == const VoidType());
return _emitTypeLiteral(type);
}
if (isSdkInternalRuntime(_currentLibrary) || node is PrimitiveConstant) {
diff --git a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
index c97c809..81efd86 100644
--- a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
@@ -8,8 +8,10 @@
import 'dart:core' as core;
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
+import 'package:_fe_analyzer_shared/src/testing/id.dart';
import 'package:_fe_analyzer_shared/src/util/link.dart';
import 'package:front_end/src/api_prototype/lowering_predicates.dart';
+import 'package:front_end/src/testing/id_extractor.dart';
import 'package:kernel/ast.dart'
hide Reference; // Work around https://github.com/dart-lang/sdk/issues/44667
import 'package:kernel/src/legacy_erasure.dart';
@@ -4742,15 +4744,20 @@
List<LocatedMessage> context;
if (whyNotPromoted != null && whyNotPromoted.isNotEmpty) {
_WhyNotPromotedVisitor whyNotPromotedVisitor =
- new _WhyNotPromotedVisitor(inferrer);
+ new _WhyNotPromotedVisitor(inferrer, receiver);
for (core.MapEntry<DartType, NonPromotionReason> entry
in whyNotPromoted.entries) {
if (entry.key.isPotentiallyNullable) continue;
- if (inferrer.dataForTesting != null) {
- inferrer.dataForTesting.flowAnalysisResult
- .nonPromotionReasons[read] = entry.value.shortName;
- }
LocatedMessage message = entry.value.accept(whyNotPromotedVisitor);
+ if (inferrer.dataForTesting != null) {
+ String nonPromotionReasonText = entry.value.shortName;
+ if (whyNotPromotedVisitor.propertyReference != null) {
+ Id id = computeMemberId(whyNotPromotedVisitor.propertyReference);
+ nonPromotionReasonText += '($id)';
+ }
+ inferrer.dataForTesting.flowAnalysisResult
+ .nonPromotionReasons[read] = nonPromotionReasonText;
+ }
context = [message];
break;
}
@@ -6987,7 +6994,11 @@
VariableDeclaration> {
final TypeInferrerImpl inferrer;
- _WhyNotPromotedVisitor(this.inferrer);
+ final Expression receiver;
+
+ Member propertyReference;
+
+ _WhyNotPromotedVisitor(this.inferrer, this.receiver);
@override
LocatedMessage visitDemoteViaExplicitWrite(
@@ -7012,10 +7023,28 @@
}
@override
- LocatedMessage visitFieldNotPromoted(FieldNotPromoted reason) {
- return templateFieldNotPromoted
- .withArguments(reason.propertyName)
- .withoutLocation();
+ LocatedMessage visitPropertyNotPromoted(PropertyNotPromoted reason) {
+ Member member;
+ Expression receiver = this.receiver;
+ if (receiver is InstanceGet) {
+ member = receiver.interfaceTarget;
+ } else if (receiver is SuperPropertyGet) {
+ member = receiver.interfaceTarget;
+ } else if (receiver is StaticInvocation) {
+ member = receiver.target;
+ } else if (receiver is PropertyGet) {
+ member = receiver.interfaceTarget;
+ } else {
+ assert(false, 'Unrecognized receiver: ${receiver.runtimeType}');
+ }
+ if (member != null) {
+ propertyReference = member;
+ return templateFieldNotPromoted
+ .withArguments(reason.propertyName)
+ .withLocation(member.fileUri, member.fileOffset, noLength);
+ } else {
+ return null;
+ }
}
}
diff --git a/pkg/vm_service/CHANGELOG.md b/pkg/vm_service/CHANGELOG.md
index 322f952..a04b1a4 100644
--- a/pkg/vm_service/CHANGELOG.md
+++ b/pkg/vm_service/CHANGELOG.md
@@ -1,6 +1,9 @@
# Changelog
## 6.1.0
+- *bug fix* Fixed issue where the root object was omitted from
+ `HeapSnapshot.classes` and the sentinel `HeapSnapshotObject` was omitted from
+ `HeapSnapshot.objects`
- Added `identityHashCode` property to `HeapSnapshotObject`, which can be used to compare
objects across heap snapshots.
- Added `successors` iterable to `HeapSnapshotObject`, which provides a convenient way to
diff --git a/pkg/vm_service/example/vm_service_assert.dart b/pkg/vm_service/example/vm_service_assert.dart
index a0938bf..040f653 100644
--- a/pkg/vm_service/example/vm_service_assert.dart
+++ b/pkg/vm_service/example/vm_service_assert.dart
@@ -331,6 +331,7 @@
assertString(obj.name!);
assertBool(obj.isAbstract!);
assertBool(obj.isConst!);
+ assertBool(obj.traceAllocations!);
assertLibraryRef(obj.library!);
assertListOfInstanceRef(obj.interfaces!);
assertListOfFieldRef(obj.fields!);
diff --git a/pkg/vm_service/java/.gitignore b/pkg/vm_service/java/.gitignore
index 8803d45..7e45717 100644
--- a/pkg/vm_service/java/.gitignore
+++ b/pkg/vm_service/java/.gitignore
@@ -5,6 +5,7 @@
src/org/dartlang/vm/service/consumer/AddBreakpointConsumer.java
src/org/dartlang/vm/service/consumer/AddBreakpointWithScriptUriConsumer.java
src/org/dartlang/vm/service/consumer/ClearCpuSamplesConsumer.java
+src/org/dartlang/vm/service/consumer/CpuSamplesConsumer.java
src/org/dartlang/vm/service/consumer/EvaluateConsumer.java
src/org/dartlang/vm/service/consumer/EvaluateInFrameConsumer.java
src/org/dartlang/vm/service/consumer/FlagListConsumer.java
@@ -36,6 +37,7 @@
src/org/dartlang/vm/service/consumer/SetFlagConsumer.java
src/org/dartlang/vm/service/consumer/SetLibraryDebuggableConsumer.java
src/org/dartlang/vm/service/consumer/SetNameConsumer.java
+src/org/dartlang/vm/service/consumer/SetTraceClassAllocationConsumer.java
src/org/dartlang/vm/service/consumer/SuccessConsumer.java
src/org/dartlang/vm/service/consumer/TimelineConsumer.java
src/org/dartlang/vm/service/consumer/TimelineFlagsConsumer.java
diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/ClientNameConsumer.java b/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/ClientNameConsumer.java
new file mode 100644
index 0000000..5ab67fb
--- /dev/null
+++ b/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/ClientNameConsumer.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2015, the Dart project authors.
+ *
+ * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.dartlang.vm.service.consumer;
+
+// This is a generated file.
+
+import org.dartlang.vm.service.element.ClientName;
+
+@SuppressWarnings({"WeakerAccess", "unused"})
+public interface ClientNameConsumer extends Consumer {
+ void received(ClientName response);
+}
diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/WebSocketTargetConsumer.java b/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/WebSocketTargetConsumer.java
new file mode 100644
index 0000000..4ce0184
--- /dev/null
+++ b/pkg/vm_service/java/src/org/dartlang/vm/service/consumer/WebSocketTargetConsumer.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2015, the Dart project authors.
+ *
+ * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.dartlang.vm.service.consumer;
+
+// This is a generated file.
+
+import org.dartlang.vm.service.element.WebSocketTarget;
+
+@SuppressWarnings({"WeakerAccess", "unused"})
+public interface WebSocketTargetConsumer extends Consumer {
+ void received(WebSocketTarget response);
+}
diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/element/ClientName.java b/pkg/vm_service/java/src/org/dartlang/vm/service/element/ClientName.java
new file mode 100644
index 0000000..179bf9a
--- /dev/null
+++ b/pkg/vm_service/java/src/org/dartlang/vm/service/element/ClientName.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2015, the Dart project authors.
+ *
+ * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.dartlang.vm.service.element;
+
+// This is a generated file.
+
+import com.google.gson.JsonObject;
+
+/**
+ * See getClientName and setClientName.
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class ClientName extends Response {
+ public ClientName(JsonObject json) {
+ super(json);
+ }
+
+ /**
+ * The name of the currently connected VM service client.
+ */
+ public String getName() {
+ return getAsString("name");
+ }
+}
diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/element/WebSocketTarget.java b/pkg/vm_service/java/src/org/dartlang/vm/service/element/WebSocketTarget.java
new file mode 100644
index 0000000..2979eb6
--- /dev/null
+++ b/pkg/vm_service/java/src/org/dartlang/vm/service/element/WebSocketTarget.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2015, the Dart project authors.
+ *
+ * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.dartlang.vm.service.element;
+
+// This is a generated file.
+
+import com.google.gson.JsonObject;
+
+/**
+ * See getWebSocketTarget
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class WebSocketTarget extends Response {
+ public WebSocketTarget(JsonObject json) {
+ super(json);
+ }
+
+ /**
+ * The web socket URI that should be used to connect to the service.
+ */
+ public String getUri() {
+ return getAsString("uri");
+ }
+}
diff --git a/pkg/vm_service/java/version.properties b/pkg/vm_service/java/version.properties
index 742d603..bdd96e7 100644
--- a/pkg/vm_service/java/version.properties
+++ b/pkg/vm_service/java/version.properties
@@ -1 +1 @@
-version=3.42
+version=3.43
diff --git a/pkg/vm_service/lib/src/snapshot_graph.dart b/pkg/vm_service/lib/src/snapshot_graph.dart
index 4db040a..e51c36b 100644
--- a/pkg/vm_service/lib/src/snapshot_graph.dart
+++ b/pkg/vm_service/lib/src/snapshot_graph.dart
@@ -97,7 +97,7 @@
/// A representation of a field captured in a memory snapshot.
class HeapSnapshotField {
- /// A 0-origin index into [HeapSnapshotObject.references].
+ /// An index into [HeapSnapshotObject.references].
int get index => _index;
/// The name of the field.
@@ -194,7 +194,7 @@
/// Data associated with this object.
dynamic get data => _data;
- /// A list of 1-origin indicies into [HeapSnapshotGraph.objects].
+ /// A list of indicies into [HeapSnapshotGraph.objects].
List<int> get references => _references;
/// The identity hash code of this object.
@@ -218,7 +218,7 @@
final HeapSnapshotGraph _graph;
final int _oid;
- int _classId = -1;
+ int _classId = 0;
int _shallowSize = -1;
int _identityHashCode = 0;
late final dynamic _data;
@@ -251,7 +251,7 @@
/// A representation of an external property captured in a memory snapshot.
class HeapSnapshotExternalProperty {
- /// A 1-origin index into [HeapSnapshotGraph.objects].
+ /// An index into [HeapSnapshotGraph.objects].
final int object;
/// The amount of external memory used.
diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart
index cc0643e..e5c1673 100644
--- a/pkg/vm_service/lib/src/vm_service.dart
+++ b/pkg/vm_service/lib/src/vm_service.dart
@@ -26,7 +26,7 @@
HeapSnapshotObjectNoData,
HeapSnapshotObjectNullData;
-const String vmServiceVersion = '3.42.0';
+const String vmServiceVersion = '3.43.0';
/// @optional
const String optional = 'optional';
@@ -199,6 +199,7 @@
'evaluate': const ['InstanceRef', 'ErrorRef'],
'evaluateInFrame': const ['InstanceRef', 'ErrorRef'],
'getAllocationProfile': const ['AllocationProfile'],
+ 'getAllocationTraces': const ['CpuSamples'],
'getClassList': const ['ClassList'],
'getCpuSamples': const ['CpuSamples'],
'getFlagList': const ['FlagList'],
@@ -232,6 +233,7 @@
'setFlag': const ['Success', 'Error'],
'setLibraryDebuggable': const ['Success'],
'setName': const ['Success'],
+ 'setTraceClassAllocation': const ['Success'],
'setVMName': const ['Success'],
'setVMTimelineFlags': const ['Success'],
'streamCancel': const ['Success'],
@@ -503,6 +505,14 @@
Future<AllocationProfile> getAllocationProfile(String isolateId,
{bool? reset, bool? gc});
+ /// See [CpuSamples].
+ Future<CpuSamples> getAllocationTraces(
+ String isolateId, {
+ int? timeOriginMicros,
+ int? timeExtentMicros,
+ String? classId,
+ });
+
/// The `getClassList` RPC is used to retrieve a `ClassList` containing all
/// classes for an isolate based on the isolate's `isolateId`.
///
@@ -1051,6 +1061,13 @@
/// returned.
Future<Success> setName(String isolateId, String name);
+ /// See [Success].
+ ///
+ /// This method will throw a [SentinelException] in the case a [Sentinel] is
+ /// returned.
+ Future<Success> setTraceClassAllocation(
+ String isolateId, String classId, bool enable);
+
/// The `setVMName` RPC is used to change the debugging name for the vm.
///
/// See [Success].
@@ -1271,6 +1288,14 @@
gc: params['gc'],
);
break;
+ case 'getAllocationTraces':
+ response = await _serviceImplementation.getAllocationTraces(
+ params!['isolateId'],
+ timeOriginMicros: params['timeOriginMicros'],
+ timeExtentMicros: params['timeExtentMicros'],
+ classId: params['classId'],
+ );
+ break;
case 'getClassList':
response = await _serviceImplementation.getClassList(
params!['isolateId'],
@@ -1447,6 +1472,13 @@
params['name'],
);
break;
+ case 'setTraceClassAllocation':
+ response = await _serviceImplementation.setTraceClassAllocation(
+ params!['isolateId'],
+ params['classId'],
+ params['enable'],
+ );
+ break;
case 'setVMName':
response = await _serviceImplementation.setVMName(
params!['name'],
@@ -1728,6 +1760,20 @@
});
@override
+ Future<CpuSamples> getAllocationTraces(
+ String isolateId, {
+ int? timeOriginMicros,
+ int? timeExtentMicros,
+ String? classId,
+ }) =>
+ _call('getAllocationTraces', {
+ 'isolateId': isolateId,
+ if (timeOriginMicros != null) 'timeOriginMicros': timeOriginMicros,
+ if (timeExtentMicros != null) 'timeExtentMicros': timeExtentMicros,
+ if (classId != null) 'classId': classId,
+ });
+
+ @override
Future<ClassList> getClassList(String isolateId) =>
_call('getClassList', {'isolateId': isolateId});
@@ -1921,6 +1967,12 @@
_call('setName', {'isolateId': isolateId, 'name': name});
@override
+ Future<Success> setTraceClassAllocation(
+ String isolateId, String classId, bool enable) =>
+ _call('setTraceClassAllocation',
+ {'isolateId': isolateId, 'classId': classId, 'enable': enable});
+
+ @override
Future<Success> setVMName(String name) => _call('setVMName', {'name': name});
@override
@@ -2837,6 +2889,9 @@
/// Is this a const class?
bool? isConst;
+ /// Are allocations of this class being traced?
+ bool? traceAllocations;
+
/// The library which contains this class.
LibraryRef? library;
@@ -2879,6 +2934,7 @@
required this.name,
required this.isAbstract,
required this.isConst,
+ required this.traceAllocations,
required this.library,
required this.interfaces,
required this.fields,
@@ -2899,6 +2955,7 @@
error = createServiceObject(json['error'], const ['ErrorRef']) as ErrorRef?;
isAbstract = json['abstract'] ?? false;
isConst = json['const'] ?? false;
+ traceAllocations = json['traceAllocations'] ?? false;
library = createServiceObject(json['library']!, const ['LibraryRef'])
as LibraryRef;
location = createServiceObject(json['location'], const ['SourceLocation'])
@@ -2934,6 +2991,7 @@
'name': name,
'abstract': isAbstract,
'const': isConst,
+ 'traceAllocations': traceAllocations,
'library': library?.toJson(),
'interfaces': interfaces?.map((f) => f.toJson()).toList(),
'fields': fields?.map((f) => f.toJson()).toList(),
@@ -3386,6 +3444,17 @@
/// @Function(foo())` `functions[stack[2]] = @Function(main())`
List<int>? stack;
+ /// The identityHashCode assigned to the allocated object. This hash code is
+ /// the same as the hash code provided in HeapSnapshot. Provided for CpuSample
+ /// instances returned from a getAllocationTraces().
+ @optional
+ int? identityHashCode;
+
+ /// Matches the index of a class in HeapSnapshot.classes. Provided for
+ /// CpuSample instances returned from a getAllocationTraces().
+ @optional
+ int? classId;
+
CpuSample({
required this.tid,
required this.timestamp,
@@ -3393,6 +3462,8 @@
this.vmTag,
this.userTag,
this.truncated,
+ this.identityHashCode,
+ this.classId,
});
CpuSample._fromJson(Map<String, dynamic> json) {
@@ -3402,6 +3473,8 @@
userTag = json['userTag'];
truncated = json['truncated'];
stack = List<int>.from(json['stack']);
+ identityHashCode = json['identityHashCode'];
+ classId = json['classId'];
}
Map<String, dynamic> toJson() {
@@ -3414,6 +3487,8 @@
_setIfNotNull(json, 'vmTag', vmTag);
_setIfNotNull(json, 'userTag', userTag);
_setIfNotNull(json, 'truncated', truncated);
+ _setIfNotNull(json, 'identityHashCode', identityHashCode);
+ _setIfNotNull(json, 'classId', classId);
return json;
}
diff --git a/pkg/vm_service/pubspec.yaml b/pkg/vm_service/pubspec.yaml
index 82aae53..f934b81 100644
--- a/pkg/vm_service/pubspec.yaml
+++ b/pkg/vm_service/pubspec.yaml
@@ -3,7 +3,7 @@
A library to communicate with a service implementing the Dart VM
service protocol.
-version: 6.1.0-dev
+version: 6.1.0
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/vm_service
diff --git a/pkg/vm_service/test/get_allocation_traces_test.dart b/pkg/vm_service/test/get_allocation_traces_test.dart
new file mode 100644
index 0000000..7dd4cc7
--- /dev/null
+++ b/pkg/vm_service/test/get_allocation_traces_test.dart
@@ -0,0 +1,124 @@
+// 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 'dart:developer';
+
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+class Foo {
+ Foo() {
+ print('Foo');
+ }
+}
+
+class Bar {
+ Bar() {
+ print('Bar');
+ }
+}
+
+void test() {
+ debugger();
+ // Toggled on for Foo.
+ debugger();
+ debugger();
+ // Traced allocation.
+ Foo();
+ // Untraced allocation.
+ Bar();
+ // Toggled on for Bar.
+ debugger();
+ debugger();
+ // Traced allocation.
+ Bar();
+ debugger();
+}
+
+Future<Class?> getClassFromRootLib(
+ VmService service,
+ IsolateRef isolateRef,
+ String className,
+) async {
+ final isolate = await service.getIsolate(isolateRef.id!);
+ Library rootLib =
+ (await service.getObject(isolate.id!, isolate.rootLib!.id!)) as Library;
+ for (ClassRef cls in rootLib.classes!) {
+ if (cls.name == className) {
+ return (await service.getObject(isolate.id!, cls.id!)) as Class;
+ }
+ }
+ return null;
+}
+
+final tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+
+ // Initial.
+ (VmService service, IsolateRef isolate) async {
+ // Verify initial state of 'Foo'.
+ Class fooClass = (await getClassFromRootLib(service, isolate, 'Foo'))!;
+ expect(fooClass.name, equals('Foo'));
+ expect(fooClass.traceAllocations, false);
+ await service.setTraceClassAllocation(isolate.id!, fooClass.id!, true);
+
+ fooClass = await service.getObject(isolate.id!, fooClass.id!) as Class;
+ expect(fooClass.traceAllocations, true);
+ },
+
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ // Extra debugger stop, continue to allow the allocation stubs to be switched
+ // over. This is a bug but low priority.
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+
+ // Allocation profile.
+ (VmService service, IsolateRef isolate) async {
+ Class fooClass = (await getClassFromRootLib(service, isolate, 'Foo'))!;
+ expect(fooClass.traceAllocations, true);
+
+ final profileResponse = await service.getAllocationTraces(isolate.id!);
+ expect(profileResponse, isNotNull);
+ expect(profileResponse.samples!.length, 1);
+ expect(profileResponse.samples!.first.identityHashCode != 0, true);
+ await service.setTraceClassAllocation(isolate.id!, fooClass.id!, false);
+
+ fooClass = await service.getObject(isolate.id!, fooClass.id!) as Class;
+ expect(fooClass.traceAllocations, false);
+ },
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ (VmService service, IsolateRef isolate) async {
+ // Trace Bar.
+ Class barClass = (await getClassFromRootLib(service, isolate, 'Bar'))!;
+ expect(barClass.traceAllocations, false);
+ await service.setTraceClassAllocation(isolate.id!, barClass.id!, true);
+ barClass = (await getClassFromRootLib(service, isolate, 'Bar'))!;
+ expect(barClass.traceAllocations, true);
+ },
+
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ // Extra debugger stop, continue to allow the allocation stubs to be switched
+ // over. This is a bug but low priority.
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+
+ (VmService service, IsolateRef isolate) async {
+ // Ensure the allocation of `Bar()` was recorded.
+ final profileResponse = (await service.getAllocationTraces(isolate.id!));
+ expect(profileResponse.samples!.length, 2);
+ },
+];
+
+main(args) async => runIsolateTests(
+ args,
+ tests,
+ "get_allocation_traces_test.dart",
+ testeeConcurrent: test,
+ );
diff --git a/runtime/observatory/lib/src/repositories/sample_profile.dart b/runtime/observatory/lib/src/repositories/sample_profile.dart
index edb45cc..0acec01 100644
--- a/runtime/observatory/lib/src/repositories/sample_profile.dart
+++ b/runtime/observatory/lib/src/repositories/sample_profile.dart
@@ -49,7 +49,7 @@
var response;
if (type == M.SampleProfileType.cpu) {
response = cls != null
- ? await cls!.getAllocationSamples()
+ ? await cls!.getAllocationTraces()
: await owner.invokeRpc('getCpuSamples', {'_code': true});
} else if (type == M.SampleProfileType.memory) {
assert(owner is M.VM);
diff --git a/runtime/observatory/lib/src/service/object.dart b/runtime/observatory/lib/src/service/object.dart
index 759d854..a3a6c49 100644
--- a/runtime/observatory/lib/src/service/object.dart
+++ b/runtime/observatory/lib/src/service/object.dart
@@ -1499,6 +1499,10 @@
return _buildClassHierarchy(classes);
}
+ Future<ServiceObject> getAllocationTraces() {
+ return invokeRpc('getAllocationTraces', {});
+ }
+
Future<ServiceObject> getPorts() {
return invokeRpc('_getPorts', {});
}
@@ -2649,7 +2653,7 @@
error = map['error'];
traceAllocations =
- (map['_traceAllocations'] != null) ? map['_traceAllocations'] : false;
+ (map['traceAllocations'] != null) ? map['traceAllocations'] : false;
}
void _addSubclass(Class subclass) {
@@ -2667,17 +2671,17 @@
}
Future<ServiceObject> setTraceAllocations(bool enable) {
- return isolate!.invokeRpc('_setTraceClassAllocation', {
+ return isolate!.invokeRpc('setTraceClassAllocation', {
'enable': enable,
'classId': id,
});
}
- Future<ServiceObject> getAllocationSamples() {
+ Future<ServiceObject> getAllocationTraces() {
var params = {
'classId': id,
};
- return isolate!.invokeRpc('_getAllocationSamples', params);
+ return isolate!.invokeRpc('getAllocationTraces', params);
}
String toString() => 'Class($vmName)';
diff --git a/runtime/observatory/tests/service/get_allocation_samples_test.dart b/runtime/observatory/tests/service/get_allocation_samples_test.dart
index 72c4e2b..5ad524a 100644
--- a/runtime/observatory/tests/service/get_allocation_samples_test.dart
+++ b/runtime/observatory/tests/service/get_allocation_samples_test.dart
@@ -54,7 +54,7 @@
var fooClass = await getClassFromRootLib(isolate, 'Foo') as Class;
await fooClass.reload();
expect(fooClass.traceAllocations, isTrue);
- dynamic profileResponse = await fooClass.getAllocationSamples();
+ dynamic profileResponse = await fooClass.getAllocationTraces();
expect(profileResponse, isNotNull);
//expect(profileResponse['type'], equals('_CpuProfile'));
await fooClass.setTraceAllocations(false);
diff --git a/runtime/observatory/tests/service/get_allocation_traces_test.dart b/runtime/observatory/tests/service/get_allocation_traces_test.dart
new file mode 100644
index 0000000..f43e26a
--- /dev/null
+++ b/runtime/observatory/tests/service/get_allocation_traces_test.dart
@@ -0,0 +1,131 @@
+// 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 'dart:developer';
+import 'package:observatory/models.dart' as M;
+import 'package:observatory/service_io.dart';
+import 'package:observatory/sample_profile.dart';
+import 'package:test/test.dart';
+import 'service_test_common.dart';
+import 'test_helper.dart';
+
+class Foo {
+ Foo() {
+ print('Foo');
+ }
+}
+
+class Bar {
+ Bar() {
+ print('Bar');
+ }
+}
+
+void test() {
+ debugger();
+ // Toggled on for Foo.
+ debugger();
+ debugger();
+ // Traced allocation.
+ Foo();
+ // Untraced allocation.
+ Bar();
+ // Toggled on for Bar.
+ debugger();
+ debugger();
+ // Traced allocation.
+ Bar();
+ debugger();
+}
+
+var tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+
+ // Initial.
+ (Isolate isolate) async {
+ // Verify initial state of 'Foo'.
+ final fooClass = (await getClassFromRootLib(isolate, 'Foo'))!;
+ expect(fooClass.name, equals('Foo'));
+ print(fooClass.id);
+ expect(fooClass.traceAllocations, isFalse);
+ await fooClass.setTraceAllocations(true);
+ await fooClass.reload();
+ expect(fooClass.traceAllocations, true);
+ },
+
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ // Extra debugger stop, continue to allow the allocation stubs to be switched
+ // over. This is a bug but low priority.
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+
+ // Allocation profile.
+ (Isolate isolate) async {
+ final fooClass = (await getClassFromRootLib(isolate, 'Foo'))!;
+ await fooClass.reload();
+ expect(fooClass.traceAllocations, true);
+
+ final profileResponse = (await isolate.getAllocationTraces()) as ServiceMap;
+ expect(profileResponse, isNotNull);
+ expect(profileResponse['type'], 'CpuSamples');
+ expect(profileResponse['samples'].length, 1);
+ expect(profileResponse['samples'][0]['identityHashCode'], isNotNull);
+ expect(profileResponse['samples'][0]['identityHashCode'] != 0, true);
+ await fooClass.setTraceAllocations(false);
+ await fooClass.reload();
+ expect(fooClass.traceAllocations, isFalse);
+
+ // Verify the allocation trace for the `Foo()` allocation.
+ final cpuProfile = SampleProfile();
+ await cpuProfile.load(isolate, profileResponse);
+ cpuProfile.buildCodeCallerAndCallees();
+ cpuProfile.buildFunctionCallerAndCallees();
+ final tree = cpuProfile.loadCodeTree(M.ProfileTreeDirection.exclusive);
+ dynamic node = tree.root;
+ final expected = [
+ 'Root',
+ '[Unoptimized] test',
+ '[Unoptimized] test',
+ '[Unoptimized] _Closure.call',
+ '[Unoptimized] _ServiceTesteeRunner.run',
+ ];
+ for (var i = 0; i < expected.length; i++) {
+ expect(node.profileCode.code.name, equals(expected[i]));
+ // Depth first traversal.
+ if (node.children.length == 0) {
+ node = null;
+ } else {
+ node = node.children[0];
+ }
+ expect(node, isNotNull);
+ }
+ },
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ (Isolate isolate) async {
+ // Trace Bar.
+ final barClass = (await getClassFromRootLib(isolate, 'Bar'))!;
+ await barClass.reload();
+ expect(barClass.traceAllocations, false);
+ await barClass.setTraceAllocations(true);
+ await barClass.reload();
+ expect(barClass.traceAllocations, true);
+ },
+
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ // Extra debugger stop, continue to allow the allocation stubs to be switched
+ // over. This is a bug but low priority.
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+
+ (Isolate isolate) async {
+ // Ensure the allocation of `Bar()` was recorded.
+ final profileResponse = (await isolate.getAllocationTraces()) as ServiceMap;
+ expect(profileResponse['samples'].length, 2);
+ },
+];
+
+main(args) async => runIsolateTests(args, tests, testeeConcurrent: test);
diff --git a/runtime/observatory_2/lib/src/repositories/sample_profile.dart b/runtime/observatory_2/lib/src/repositories/sample_profile.dart
index 944eaba..d0fba57 100644
--- a/runtime/observatory_2/lib/src/repositories/sample_profile.dart
+++ b/runtime/observatory_2/lib/src/repositories/sample_profile.dart
@@ -49,7 +49,7 @@
var response;
if (type == M.SampleProfileType.cpu) {
response = cls != null
- ? await cls.getAllocationSamples()
+ ? await cls.getAllocationTraces()
: await owner.invokeRpc('getCpuSamples', {'_code': true});
} else if (type == M.SampleProfileType.memory) {
assert(owner is M.VM);
diff --git a/runtime/observatory_2/lib/src/service/object.dart b/runtime/observatory_2/lib/src/service/object.dart
index da5b60f..bedbd36 100644
--- a/runtime/observatory_2/lib/src/service/object.dart
+++ b/runtime/observatory_2/lib/src/service/object.dart
@@ -1505,6 +1505,10 @@
return _buildClassHierarchy(classes);
}
+ Future<ServiceObject> getAllocationTraces() {
+ return invokeRpc('getAllocationTraces', {});
+ }
+
Future<ServiceObject> getPorts() {
return invokeRpc('_getPorts', {});
}
@@ -2658,7 +2662,7 @@
error = map['error'];
traceAllocations =
- (map['_traceAllocations'] != null) ? map['_traceAllocations'] : false;
+ (map['traceAllocations'] != null) ? map['traceAllocations'] : false;
}
void _addSubclass(Class subclass) {
@@ -2676,17 +2680,17 @@
}
Future<ServiceObject> setTraceAllocations(bool enable) {
- return isolate.invokeRpc('_setTraceClassAllocation', {
+ return isolate.invokeRpc('setTraceClassAllocation', {
'enable': enable,
'classId': id,
});
}
- Future<ServiceObject> getAllocationSamples() {
+ Future<ServiceObject> getAllocationTraces() {
var params = {
'classId': id,
};
- return isolate.invokeRpc('_getAllocationSamples', params);
+ return isolate.invokeRpc('getAllocationTraces', params);
}
String toString() => 'Class($vmName)';
diff --git a/runtime/observatory_2/tests/service_2/get_allocation_samples_test.dart b/runtime/observatory_2/tests/service_2/get_allocation_samples_test.dart
index d39219d..0b0e237 100644
--- a/runtime/observatory_2/tests/service_2/get_allocation_samples_test.dart
+++ b/runtime/observatory_2/tests/service_2/get_allocation_samples_test.dart
@@ -54,7 +54,7 @@
var fooClass = await getClassFromRootLib(isolate, 'Foo');
await fooClass.reload();
expect(fooClass.traceAllocations, isTrue);
- dynamic profileResponse = await fooClass.getAllocationSamples();
+ dynamic profileResponse = await fooClass.getAllocationTraces();
expect(profileResponse, isNotNull);
expect(profileResponse['type'], equals('CpuSamples'));
await fooClass.setTraceAllocations(false);
diff --git a/runtime/observatory_2/tests/service_2/get_allocation_traces_test.dart b/runtime/observatory_2/tests/service_2/get_allocation_traces_test.dart
new file mode 100644
index 0000000..dadf082
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/get_allocation_traces_test.dart
@@ -0,0 +1,132 @@
+// 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 'dart:developer';
+import 'package:observatory_2/models.dart' as M;
+import 'package:observatory_2/service_io.dart';
+import 'package:observatory_2/sample_profile.dart';
+import 'package:test/test.dart';
+import 'service_test_common.dart';
+import 'test_helper.dart';
+
+class Foo {
+ Foo() {
+ print('Foo');
+ }
+}
+
+class Bar {
+ Bar() {
+ print('Bar');
+ }
+}
+
+void test() {
+ debugger();
+ // Toggled on for Foo.
+ debugger();
+ debugger();
+ // Traced allocation.
+ Foo();
+ // Untraced allocation.
+ Bar();
+ // Toggled on for Bar.
+ debugger();
+ debugger();
+ // Traced allocation.
+ Bar();
+ debugger();
+}
+
+var tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+
+ // Initial.
+ (Isolate isolate) async {
+ // Verify initial state of 'Foo'.
+ final fooClass = await getClassFromRootLib(isolate, 'Foo');
+ expect(fooClass, isNotNull);
+ expect(fooClass.name, equals('Foo'));
+ print(fooClass.id);
+ expect(fooClass.traceAllocations, isFalse);
+ await fooClass.setTraceAllocations(true);
+ await fooClass.reload();
+ expect(fooClass.traceAllocations, true);
+ },
+
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ // Extra debugger stop, continue to allow the allocation stubs to be switched
+ // over. This is a bug but low priority.
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+
+ // Allocation profile.
+ (Isolate isolate) async {
+ final fooClass = await getClassFromRootLib(isolate, 'Foo');
+ await fooClass.reload();
+ expect(fooClass.traceAllocations, true);
+
+ final profileResponse = (await isolate.getAllocationTraces()) as ServiceMap;
+ expect(profileResponse, isNotNull);
+ expect(profileResponse['type'], 'CpuSamples');
+ expect(profileResponse['samples'].length, 1);
+ expect(profileResponse['samples'][0]['identityHashCode'], isNotNull);
+ expect(profileResponse['samples'][0]['identityHashCode'] != 0, true);
+ await fooClass.setTraceAllocations(false);
+ await fooClass.reload();
+ expect(fooClass.traceAllocations, isFalse);
+
+ // Verify the allocation trace for the `Foo()` allocation.
+ final cpuProfile = SampleProfile();
+ await cpuProfile.load(isolate, profileResponse);
+ cpuProfile.buildCodeCallerAndCallees();
+ cpuProfile.buildFunctionCallerAndCallees();
+ final tree = cpuProfile.loadCodeTree(M.ProfileTreeDirection.exclusive);
+ var node = tree.root;
+ final expected = [
+ 'Root',
+ '[Unoptimized] test',
+ '[Unoptimized] test',
+ '[Unoptimized] _Closure.call',
+ '[Unoptimized] _ServiceTesteeRunner.run',
+ ];
+ for (var i = 0; i < expected.length; i++) {
+ expect(node.profileCode.code.name, equals(expected[i]));
+ // Depth first traversal.
+ if (node.children.length == 0) {
+ node = null;
+ } else {
+ node = node.children[0];
+ }
+ expect(node, isNotNull);
+ }
+ },
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ (Isolate isolate) async {
+ // Trace Bar.
+ final barClass = await getClassFromRootLib(isolate, 'Bar');
+ await barClass.reload();
+ expect(barClass.traceAllocations, false);
+ await barClass.setTraceAllocations(true);
+ await barClass.reload();
+ expect(barClass.traceAllocations, true);
+ },
+
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ // Extra debugger stop, continue to allow the allocation stubs to be switched
+ // over. This is a bug but low priority.
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+
+ (Isolate isolate) async {
+ // Ensure the allocation of `Bar()` was recorded.
+ final profileResponse = (await isolate.getAllocationTraces()) as ServiceMap;
+ expect(profileResponse['samples'].length, 2);
+ },
+];
+
+main(args) async => runIsolateTests(args, tests, testeeConcurrent: test);
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.cc b/runtime/vm/compiler/backend/flow_graph_compiler.cc
index 6fac0ad..f5423f3c 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.cc
@@ -2039,7 +2039,7 @@
} else if (parsed_function_.function().IsIrregexpFunction()) {
threshold = FLAG_regexp_optimization_counter_threshold;
} else if (FLAG_randomize_optimization_counter) {
- threshold = Thread::Current()->GetRandomUInt64() %
+ threshold = Thread::Current()->random()->NextUInt64() %
FLAG_optimization_counter_threshold;
} else {
const intptr_t basic_blocks = flow_graph().preorder().length();
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 33ac31c..15d93cc 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -46,6 +46,7 @@
#include "vm/kernel_isolate.h"
#include "vm/kernel_loader.h"
#include "vm/native_symbol.h"
+#include "vm/object_graph.h"
#include "vm/object_store.h"
#include "vm/parser.h"
#include "vm/profiler.h"
@@ -2586,28 +2587,31 @@
OUT_OF_MEMORY();
}
}
-#ifndef PRODUCT
- auto class_table = thread->isolate_group()->shared_class_table();
- if (class_table->TraceAllocationFor(cls_id)) {
- Profiler::SampleAllocation(thread, cls_id);
- }
-#endif // !PRODUCT
NoSafepointScope no_safepoint;
+ ObjectPtr raw_obj;
InitializeObject(address, cls_id, size);
- ObjectPtr raw_obj = static_cast<ObjectPtr>(address + kHeapObjectTag);
+ raw_obj = static_cast<ObjectPtr>(address + kHeapObjectTag);
ASSERT(cls_id == UntaggedObject::ClassIdTag::decode(raw_obj->untag()->tags_));
if (raw_obj->IsOldObject() && UNLIKELY(thread->is_marking())) {
- // Black allocation. Prevents a data race between the mutator and concurrent
- // marker on ARM and ARM64 (the marker may observe a publishing store of
- // this object before the stores that initialize its slots), and helps the
- // collection to finish sooner.
+ // Black allocation. Prevents a data race between the mutator and
+ // concurrent marker on ARM and ARM64 (the marker may observe a
+ // publishing store of this object before the stores that initialize its
+ // slots), and helps the collection to finish sooner.
raw_obj->untag()->SetMarkBitUnsynchronized();
- // Setting the mark bit must not be ordered after a publishing store of this
- // object. Adding a barrier here is cheaper than making every store into the
- // heap a store-release. Compare Scavenger::ScavengePointer.
+ // Setting the mark bit must not be ordered after a publishing store of
+ // this object. Adding a barrier here is cheaper than making every store
+ // into the heap a store-release. Compare Scavenger::ScavengePointer.
std::atomic_thread_fence(std::memory_order_release);
heap->old_space()->AllocateBlack(size);
}
+#ifndef PRODUCT
+ auto class_table = thread->isolate_group()->shared_class_table();
+ if (class_table->TraceAllocationFor(cls_id)) {
+ uint32_t hash =
+ HeapSnapshotWriter::GetHeapSnapshotIdentityHash(thread, raw_obj);
+ Profiler::SampleAllocation(thread, cls_id, hash);
+ }
+#endif // !PRODUCT
return raw_obj;
}
diff --git a/runtime/vm/object_graph.cc b/runtime/vm/object_graph.cc
index f71a174..e66e67b 100644
--- a/runtime/vm/object_graph.cc
+++ b/runtime/vm/object_graph.cc
@@ -1013,86 +1013,22 @@
DISALLOW_COPY_AND_ASSIGN(Pass2Visitor);
};
+
class Pass3Visitor : public ObjectVisitor {
public:
explicit Pass3Visitor(HeapSnapshotWriter* writer)
- : ObjectVisitor(), isolate_(Isolate::Current()), writer_(writer) {}
+ : ObjectVisitor(), thread_(Thread::Current()), writer_(writer) {}
void VisitObject(ObjectPtr obj) {
if (obj->IsPseudoObject()) {
return;
}
- writer_->WriteUnsigned(GetHash(obj));
+ writer_->WriteUnsigned(
+ HeapSnapshotWriter::GetHeapSnapshotIdentityHash(thread_, obj));
}
private:
- uint32_t GetHash(ObjectPtr obj) {
- if (!obj->IsHeapObject()) return 0;
- intptr_t cid = obj->GetClassId();
- uint32_t hash = 0;
- switch (cid) {
- case kForwardingCorpse:
- case kFreeListElement:
- case kSmiCid:
- UNREACHABLE();
- case kArrayCid:
- case kBoolCid:
- case kCodeSourceMapCid:
- case kCompressedStackMapsCid:
- case kDoubleCid:
- case kExternalOneByteStringCid:
- case kExternalTwoByteStringCid:
- case kGrowableObjectArrayCid:
- case kImmutableArrayCid:
- case kInstructionsCid:
- case kInstructionsSectionCid:
- case kLinkedHashMapCid:
- case kMintCid:
- case kNeverCid:
- case kNullCid:
- case kObjectPoolCid:
- case kOneByteStringCid:
- case kPcDescriptorsCid:
- case kTwoByteStringCid:
- case kVoidCid:
- // Don't provide hash codes for objects with the above CIDs in order
- // to try and avoid having to initialize identity hash codes for common
- // primitives and types that don't have hash codes.
- break;
- default: {
- hash = GetHashHelper(obj);
- }
- }
- return hash;
- }
-
- uint32_t GetHashHelper(ObjectPtr obj) {
- uint32_t hash;
-#if defined(HASH_IN_OBJECT_HEADER)
- hash = Object::GetCachedHash(obj);
- if (hash == 0) {
- ASSERT(
- !isolate_->group()->heap()->old_space()->IsObjectFromImagePages(obj));
- hash = isolate_->random()->NextUInt32();
- Object::SetCachedHash(obj, hash);
- hash = Object::GetCachedHash(obj);
- }
-#else
- Heap* heap = isolate_->group()->heap();
- hash = heap->GetHash(obj);
- if (hash == 0) {
- ASSERT(!heap->old_space()->IsObjectFromImagePages(obj));
- heap->SetHash(obj, isolate_->random()->NextUInt32());
- hash = heap->GetHash(obj);
- }
-#endif
- return hash;
- }
-
- // TODO(dartbug.com/36097): Once the shared class table contains more
- // information than just the size (i.e. includes an immutable class
- // descriptor), we can remove this dependency on the current isolate.
- Isolate* isolate_;
+ Thread* thread_;
HeapSnapshotWriter* const writer_;
DISALLOW_COPY_AND_ASSIGN(Pass3Visitor);
@@ -1281,6 +1217,68 @@
Flush(true);
}
+uint32_t HeapSnapshotWriter::GetHeapSnapshotIdentityHash(Thread* thread,
+ ObjectPtr obj) {
+ if (!obj->IsHeapObject()) return 0;
+ intptr_t cid = obj->GetClassId();
+ uint32_t hash = 0;
+ switch (cid) {
+ case kForwardingCorpse:
+ case kFreeListElement:
+ case kSmiCid:
+ UNREACHABLE();
+ case kArrayCid:
+ case kBoolCid:
+ case kCodeSourceMapCid:
+ case kCompressedStackMapsCid:
+ case kDoubleCid:
+ case kExternalOneByteStringCid:
+ case kExternalTwoByteStringCid:
+ case kGrowableObjectArrayCid:
+ case kImmutableArrayCid:
+ case kInstructionsCid:
+ case kInstructionsSectionCid:
+ case kLinkedHashMapCid:
+ case kMintCid:
+ case kNeverCid:
+ case kNullCid:
+ case kObjectPoolCid:
+ case kOneByteStringCid:
+ case kPcDescriptorsCid:
+ case kTwoByteStringCid:
+ case kVoidCid:
+ // Don't provide hash codes for objects with the above CIDs in order
+ // to try and avoid having to initialize identity hash codes for common
+ // primitives and types that don't have hash codes.
+ break;
+ default: {
+ hash = GetHashHelper(thread, obj);
+ }
+ }
+ return hash;
+}
+
+uint32_t HeapSnapshotWriter::GetHashHelper(Thread* thread, ObjectPtr obj) {
+ uint32_t hash;
+#if defined(HASH_IN_OBJECT_HEADER)
+ hash = Object::GetCachedHash(obj);
+ if (hash == 0) {
+ ASSERT(!thread->heap()->old_space()->IsObjectFromImagePages(obj));
+ hash = thread->random()->NextUInt32();
+ Object::SetCachedHash(obj, hash);
+ }
+#else
+ Heap* heap = thread->heap();
+ hash = heap->GetHash(obj);
+ if (hash == 0) {
+ ASSERT(!heap->old_space()->IsObjectFromImagePages(obj));
+ hash = thread->random()->NextUInt32();
+ heap->SetHash(obj, hash);
+ }
+#endif
+ return hash;
+}
+
CountObjectsVisitor::CountObjectsVisitor(Thread* thread, intptr_t class_count)
: ObjectVisitor(),
HandleVisitor(thread),
diff --git a/runtime/vm/object_graph.h b/runtime/vm/object_graph.h
index 175e084..d034705 100644
--- a/runtime/vm/object_graph.h
+++ b/runtime/vm/object_graph.h
@@ -186,7 +186,11 @@
void Write();
+ static uint32_t GetHeapSnapshotIdentityHash(Thread* thread, ObjectPtr obj);
+
private:
+ static uint32_t GetHashHelper(Thread* thread, ObjectPtr obj);
+
static const intptr_t kMetadataReservation = 512;
static const intptr_t kPreferredChunkSize = MB;
diff --git a/runtime/vm/object_service.cc b/runtime/vm/object_service.cc
index 89e394b..3989db4 100644
--- a/runtime/vm/object_service.cc
+++ b/runtime/vm/object_service.cc
@@ -99,7 +99,7 @@
jsobj.AddProperty("_finalized", is_finalized());
jsobj.AddProperty("_implemented", is_implemented());
jsobj.AddProperty("_patch", false);
- jsobj.AddProperty("_traceAllocations", TraceAllocation(isolate->group()));
+ jsobj.AddProperty("traceAllocations", TraceAllocation(isolate->group()));
const Class& superClass = Class::Handle(SuperClass());
if (!superClass.IsNull()) {
diff --git a/runtime/vm/profiler.cc b/runtime/vm/profiler.cc
index 0c7cad8..f0d9d2c 100644
--- a/runtime/vm/profiler.cc
+++ b/runtime/vm/profiler.cc
@@ -1137,7 +1137,9 @@
}
}
-void Profiler::SampleAllocation(Thread* thread, intptr_t cid) {
+void Profiler::SampleAllocation(Thread* thread,
+ intptr_t cid,
+ uint32_t identity_hash) {
ASSERT(thread != NULL);
OSThread* os_thread = thread->os_thread();
ASSERT(os_thread != NULL);
@@ -1175,6 +1177,7 @@
Sample* sample = SetupSample(thread, sample_buffer, os_thread->trace_id());
sample->SetAllocationCid(cid);
+ sample->set_allocation_identity_hash(identity_hash);
if (FLAG_profile_vm_allocation) {
ProfilerNativeStackWalker native_stack_walker(
@@ -1561,6 +1564,8 @@
processed_sample->set_user_tag(sample->user_tag());
if (sample->is_allocation_sample()) {
processed_sample->set_allocation_cid(sample->allocation_cid());
+ processed_sample->set_allocation_identity_hash(
+ sample->allocation_identity_hash());
}
processed_sample->set_first_frame_executing(!sample->exit_frame_sample());
@@ -1612,6 +1617,7 @@
vm_tag_(0),
user_tag_(0),
allocation_cid_(-1),
+ allocation_identity_hash_(0),
truncated_(false),
timeline_code_trie_(nullptr),
timeline_function_trie_(nullptr) {}
diff --git a/runtime/vm/profiler.h b/runtime/vm/profiler.h
index 5f24c2a..215d357 100644
--- a/runtime/vm/profiler.h
+++ b/runtime/vm/profiler.h
@@ -77,7 +77,9 @@
static void DumpStackTrace(void* context);
static void DumpStackTrace(bool for_crash = true);
- static void SampleAllocation(Thread* thread, intptr_t cid);
+ static void SampleAllocation(Thread* thread,
+ intptr_t cid,
+ uint32_t identity_hash);
static Sample* SampleNativeAllocation(intptr_t skip_count,
uword address,
uintptr_t allocation_size);
@@ -210,6 +212,7 @@
lr_ = 0;
metadata_ = 0;
state_ = 0;
+ allocation_identity_hash_ = 0;
native_allocation_address_ = 0;
native_allocation_size_bytes_ = 0;
continuation_index_ = -1;
@@ -315,6 +318,14 @@
state_ = ClassAllocationSampleBit::update(allocation_sample, state_);
}
+ uint32_t allocation_identity_hash() const {
+ return allocation_identity_hash_;
+ }
+
+ void set_allocation_identity_hash(uint32_t hash) {
+ allocation_identity_hash_ = hash;
+ }
+
uword native_allocation_address() const { return native_allocation_address_; }
void set_native_allocation_address(uword address) {
@@ -423,6 +434,7 @@
uword metadata_;
uword lr_;
uword state_;
+ uint32_t allocation_identity_hash_;
uword native_allocation_address_;
uintptr_t native_allocation_size_bytes_;
intptr_t continuation_index_;
@@ -747,6 +759,15 @@
intptr_t allocation_cid() const { return allocation_cid_; }
void set_allocation_cid(intptr_t cid) { allocation_cid_ = cid; }
+ // The identity hash code of the allocated object if this is an allocation
+ // profile sample. -1 otherwise.
+ uint32_t allocation_identity_hash() const {
+ return allocation_identity_hash_;
+ }
+ void set_allocation_identity_hash(uint32_t hash) {
+ allocation_identity_hash_ = hash;
+ }
+
bool IsAllocationSample() const { return allocation_cid_ > 0; }
bool is_native_allocation_sample() const {
@@ -800,6 +821,7 @@
uword vm_tag_;
uword user_tag_;
intptr_t allocation_cid_;
+ uint32_t allocation_identity_hash_;
bool truncated_;
bool first_frame_executing_;
uword native_allocation_address_;
diff --git a/runtime/vm/profiler_service.cc b/runtime/vm/profiler_service.cc
index c5d4619..599885b 100644
--- a/runtime/vm/profiler_service.cc
+++ b/runtime/vm/profiler_service.cc
@@ -1704,6 +1704,11 @@
PrintCodeFrameIndexJSON(&stack, sample, frame_index);
}
}
+ if (sample->IsAllocationSample()) {
+ sample_obj.AddProperty64("classId", sample->allocation_cid());
+ sample_obj.AddProperty64("identityHashCode",
+ sample->allocation_identity_hash());
+ }
}
}
@@ -1789,6 +1794,30 @@
include_code_samples);
}
+class AllocationSampleFilter : public SampleFilter {
+ public:
+ AllocationSampleFilter(Dart_Port port,
+ intptr_t thread_task_mask,
+ int64_t time_origin_micros,
+ int64_t time_extent_micros)
+ : SampleFilter(port,
+ thread_task_mask,
+ time_origin_micros,
+ time_extent_micros) {}
+
+ bool FilterSample(Sample* sample) { return sample->is_allocation_sample(); }
+};
+
+void ProfilerService::PrintAllocationJSON(JSONStream* stream,
+ int64_t time_origin_micros,
+ int64_t time_extent_micros) {
+ Thread* thread = Thread::Current();
+ Isolate* isolate = thread->isolate();
+ AllocationSampleFilter filter(isolate->main_port(), Thread::kMutatorTask,
+ time_origin_micros, time_extent_micros);
+ PrintJSONImpl(thread, stream, &filter, Profiler::sample_buffer(), true);
+}
+
class ClassAllocationSampleFilter : public SampleFilter {
public:
ClassAllocationSampleFilter(Dart_Port port,
diff --git a/runtime/vm/profiler_service.h b/runtime/vm/profiler_service.h
index a3d42a8..8009207 100644
--- a/runtime/vm/profiler_service.h
+++ b/runtime/vm/profiler_service.h
@@ -430,6 +430,10 @@
int64_t time_origin_micros,
int64_t time_extent_micros);
+ static void PrintAllocationJSON(JSONStream* stream,
+ int64_t time_origin_micros,
+ int64_t time_extent_micros);
+
static void PrintNativeAllocationJSON(JSONStream* stream,
int64_t time_origin_micros,
int64_t time_extent_micros,
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index ae26db2..7d41f1a 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -4015,7 +4015,7 @@
return true;
}
-static const MethodParameter* get_allocation_samples_params[] = {
+static const MethodParameter* get_allocation_traces_params[] = {
RUNNABLE_ISOLATE_PARAMETER,
new IdParameter("classId", false),
new Int64Parameter("timeOriginMicros", false),
@@ -4023,24 +4023,35 @@
NULL,
};
-static bool GetAllocationSamples(Thread* thread, JSONStream* js) {
+static bool GetAllocationTraces(Thread* thread, JSONStream* js) {
int64_t time_origin_micros =
Int64Parameter::Parse(js->LookupParam("timeOriginMicros"));
int64_t time_extent_micros =
Int64Parameter::Parse(js->LookupParam("timeExtentMicros"));
- const char* class_id = js->LookupParam("classId");
- intptr_t cid = -1;
- GetPrefixedIntegerId(class_id, "classes/", &cid);
Isolate* isolate = thread->isolate();
- if (IsValidClassId(isolate, cid)) {
+
+ // Return only allocations for objects with classId.
+ if (js->HasParam("classId")) {
+ const char* class_id = js->LookupParam("classId");
+ intptr_t cid = -1;
+ GetPrefixedIntegerId(class_id, "classes/", &cid);
+ if (IsValidClassId(isolate, cid)) {
+ if (CheckProfilerDisabled(thread, js)) {
+ return true;
+ }
+ const Class& cls = Class::Handle(GetClassForId(isolate, cid));
+ ProfilerService::PrintAllocationJSON(js, cls, time_origin_micros,
+ time_extent_micros);
+ } else {
+ PrintInvalidParamError(js, "classId");
+ }
+ } else {
+ // Otherwise, return allocations for all traced class IDs.
if (CheckProfilerDisabled(thread, js)) {
return true;
}
- const Class& cls = Class::Handle(GetClassForId(isolate, cid));
- ProfilerService::PrintAllocationJSON(js, cls, time_origin_micros,
+ ProfilerService::PrintAllocationJSON(js, time_origin_micros,
time_extent_micros);
- } else {
- PrintInvalidParamError(js, "classId");
}
return true;
}
@@ -5092,8 +5103,8 @@
get_allocation_profile_params },
{ "getAllocationProfile", GetAllocationProfilePublic,
get_allocation_profile_params },
- { "_getAllocationSamples", GetAllocationSamples,
- get_allocation_samples_params },
+ { "getAllocationTraces", GetAllocationTraces,
+ get_allocation_traces_params },
{ "_getNativeAllocationSamples", GetNativeAllocationSamples,
get_native_allocation_samples_params },
{ "getClassList", GetClassList,
@@ -5188,7 +5199,7 @@
set_library_debuggable_params },
{ "setName", SetName,
set_name_params },
- { "_setTraceClassAllocation", SetTraceClassAllocation,
+ { "setTraceClassAllocation", SetTraceClassAllocation,
set_trace_class_allocation_params },
{ "setVMName", SetVMName,
set_vm_name_params },
diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md
index 39e71d2..9df4745 100644
--- a/runtime/vm/service/service.md
+++ b/runtime/vm/service/service.md
@@ -39,6 +39,7 @@
- [evaluate](#evaluate)
- [evaluateInFrame](#evaluateinframe)
- [getAllocationProfile](#getallocationprofile)
+ - [getAllocationTraces](#getallocationtraces)
- [getCpuSamples](#getcpusamples)
- [getFlagList](#getflaglist)
- [getInstances](#getinstances)
@@ -70,6 +71,7 @@
- [setFlag](#setflag)
- [setLibraryDebuggable](#setlibrarydebuggable)
- [setName](#setname)
+ - [setTraceClassAllocation](#settraceclassallocation)
- [setVMName](#setvmname)
- [setVMTimelineFlags](#setvmtimelineflags)
- [streamCancel](#streamcancel)
@@ -728,6 +730,26 @@
If _isolateId_ refers to an isolate which has exited, then the
_Collected_ [Sentinel](#sentinel) is returned.
+### getAllocationTraces
+
+The _getAllocationTraces_ RPC allows for the retrieval of allocation traces for objects of a
+specific set of types (see [setTraceClassAllocation](#setTraceClassAllocation)). Only samples
+collected in the time range `[timeOriginMicros, timeOriginMicros + timeExtentMicros]` will be
+reported.
+
+If `classId` is provided, only traces for allocations with the matching `classId` will be
+reported.
+
+If the profiler is disabled, an RPC error response will be returned.
+
+If isolateId refers to an isolate which has exited, then the Collected Sentinel is returned.
+
+```
+CpuSamples getAllocationTraces(string isolateId, int timeOriginMicros [optional], int timeExtentMicros [optional], string classId [optional])
+```
+
+See [CpuSamples](#cpusamples).
+
### getClassList
```
@@ -1361,6 +1383,20 @@
See [Success](#success).
+### setTraceClassAllocation
+
+The _setTraceClassAllocation_ RPC allows for enabling or disabling allocation tracing for a specific type of object. Allocation traces can be retrieved with the _getAllocationTraces_ RPC.
+
+If `enable` is true, allocations of objects of the class represented by `classId` will be traced.
+
+If `isolateId` refers to an isolate which has exited, then the _Collected_ [Sentinel](#sentinel) is returned.
+
+```
+Success|Sentinel setTraceClassAllocation(string isolateId, string classId, bool enable)
+```
+
+See [Success](#success).
+
### setVMName
```
@@ -1648,6 +1684,9 @@
// Is this a const class?
bool const;
+ // Are allocations of this class being traced?
+ bool traceAllocations;
+
// The library which contains this class.
@Library library;
@@ -1861,6 +1900,15 @@
// `functions[stack[1]] = @Function(foo())`
// `functions[stack[2]] = @Function(main())`
int[] stack;
+
+ // The identityHashCode assigned to the allocated object. This hash
+ // code is the same as the hash code provided in HeapSnapshot. Provided for
+ // CpuSample instances returned from a getAllocationTraces().
+ int identityHashCode [optional];
+
+ // Matches the index of a class in HeapSnapshot.classes. Provided for
+ // CpuSample instances returned from a getAllocationTraces().
+ int classId [optional];
}
```
@@ -3948,6 +3996,8 @@
3.40 | Added `IsolateFlag` object and `isolateFlags` property to `Isolate`.
3.41 | Added `PortList` object, `ReceivePort` `InstanceKind`, and `getPorts` RPC.
3.42 | Added `limit` optional parameter to `getStack` RPC.
-3.43 | Updated heap snapshot format to include identity hash codes.
+3.43 | Updated heap snapshot format to include identity hash codes. Added `getAllocationTraces`
+and `setTraceClassAllocation` RPCs, updated `CpuSample` to include `identityHashCode` and
+`classId` properties, updated `Class` to include `traceAllocations` property.
[discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss
diff --git a/runtime/vm/thread.h b/runtime/vm/thread.h
index 0df08ee..43372a6 100644
--- a/runtime/vm/thread.h
+++ b/runtime/vm/thread.h
@@ -887,7 +887,7 @@
void InitVMConstants();
- uint64_t GetRandomUInt64() { return thread_random_.NextUInt64(); }
+ Random* random() { return &thread_random_; }
uint64_t* GetFfiMarshalledArguments(intptr_t size) {
if (ffi_marshalled_arguments_size_ < size) {
diff --git a/tools/VERSION b/tools/VERSION
index 8e7da84..65a92de 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 13
PATCH 0
-PRERELEASE 32
+PRERELEASE 33
PRERELEASE_PATCH 0
\ No newline at end of file