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