Version 2.14.0-130.0.dev

Merge commit 'b9a0c0dfba993e726c36c255470af9868b65a6e5' into 'dev'
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/import_library.dart b/pkg/analysis_server/lib/src/services/correction/dart/import_library.dart
index 1499ae6..6c90568 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/import_library.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/import_library.dart
@@ -8,6 +8,7 @@
 import 'package:analysis_server/src/services/correction/fix.dart';
 import 'package:analysis_server/src/services/correction/fix/dart/top_level_declarations.dart';
 import 'package:analysis_server/src/services/correction/namespace.dart';
+import 'package:analysis_server/src/services/linter/lint_names.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/source/source_range.dart';
@@ -110,26 +111,44 @@
     return false;
   }
 
-  /// Return the relative uri from the passed [library] to the given [path].
-  /// If the [path] is not in the LibraryElement, `null` is returned.
-  String? _getRelativeURIFromLibrary(LibraryElement library, String path) {
+  /// Returns the relative URI from the passed [library] to the given [path].
+  ///
+  /// If the [path] is not in the [library]'s directory, `null` is returned.
+  String? _getRelativeUriFromLibrary(LibraryElement library, String path) {
     var librarySource = library.librarySource;
-    var pathCtx = resourceProvider.pathContext;
-    var libraryDirectory = pathCtx.dirname(librarySource.fullName);
-    var sourceDirectory = pathCtx.dirname(path);
-    if (pathCtx.isWithin(libraryDirectory, path) ||
-        pathCtx.isWithin(sourceDirectory, libraryDirectory)) {
-      var relativeFile = pathCtx.relative(path, from: libraryDirectory);
-      return pathCtx.split(relativeFile).join('/');
+    var pathContext = resourceProvider.pathContext;
+    var libraryDirectory = pathContext.dirname(librarySource.fullName);
+    var sourceDirectory = pathContext.dirname(path);
+    if (pathContext.isWithin(libraryDirectory, path) ||
+        pathContext.isWithin(sourceDirectory, libraryDirectory)) {
+      var relativeFile = pathContext.relative(path, from: libraryDirectory);
+      return pathContext.split(relativeFile).join('/');
     }
     return null;
   }
 
+  /// Returns a list of one or two import corrections.
+  ///
+  /// If [relativeUri] is `null`, only one correction, with an absolute import
+  /// path, is returned. Otherwise, a correction with an absolute import path
+  /// and a correction with a relative path are returned. If the
+  /// `prefer_relative_imports` lint rule is enabled, the relative path is
+  /// returned first.
   Iterable<CorrectionProducer> _importLibrary(FixKind fixKind, Uri library,
-      [String? relativeURI]) sync* {
-    yield _ImportAbsoluteLibrary(fixKind, library);
-    if (relativeURI != null && relativeURI.isNotEmpty) {
-      yield _ImportRelativeLibrary(fixKind, relativeURI);
+      [String? relativeUri]) {
+    if (relativeUri == null || relativeUri.isEmpty) {
+      return [_ImportAbsoluteLibrary(fixKind, library)];
+    }
+    if (isLintEnabled(LintNames.prefer_relative_imports)) {
+      return [
+        _ImportRelativeLibrary(fixKind, relativeUri),
+        _ImportAbsoluteLibrary(fixKind, library),
+      ];
+    } else {
+      return [
+        _ImportAbsoluteLibrary(fixKind, library),
+        _ImportRelativeLibrary(fixKind, relativeUri),
+      ];
     }
   }
 
@@ -215,9 +234,9 @@
         fixKind = DartFixKind.IMPORT_LIBRARY_PROJECT1;
       }
       // Add the fix.
-      var relativeURI =
-          _getRelativeURIFromLibrary(libraryElement, declaration.path);
-      yield* _importLibrary(fixKind, declaration.uri, relativeURI);
+      var relativeUri =
+          _getRelativeUriFromLibrary(libraryElement, declaration.path);
+      yield* _importLibrary(fixKind, declaration.uri, relativeUri);
     }
   }
 
diff --git a/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart b/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart
index aab237e..7ac6c2b 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart
@@ -232,6 +232,22 @@
     expect(resultCode, expected);
   }
 
+  /// Computes an error from [errorFilter], and verifies that
+  /// [expectedNumberOfFixesForKind] fixes of the appropriate kind are found,
+  /// and that they have messages equal to [matchFixMessages].
+  Future<void> assertHasFixesWithoutApplying({
+    bool Function(AnalysisError)? errorFilter,
+    required int expectedNumberOfFixesForKind,
+    required List<String> matchFixMessages,
+  }) async {
+    var error = await _findErrorToFix(errorFilter: errorFilter);
+    await _assertHasFixes(
+      error,
+      expectedNumberOfFixesForKind: expectedNumberOfFixesForKind,
+      matchFixMessages: matchFixMessages,
+    );
+  }
+
   Future<void> assertHasFixWithoutApplying(
       {bool Function(AnalysisError)? errorFilter}) async {
     var error = await _findErrorToFix(errorFilter: errorFilter);
@@ -283,9 +299,14 @@
     useLineEndingsForPlatform = true;
   }
 
-  /// Computes fixes and verifies that there is a fix for the given [error] of the appropriate kind.
-  /// Optionally, if a [matchFixMessage] is passed, then the kind as well as the fix message must
-  /// match to be returned.
+  /// Computes fixes, verifies that there is a fix for the given [error] of
+  /// the appropriate kind, and returns the fix.
+  ///
+  /// If a [matchFixMessage] is passed, then the kind as well as the fix message
+  /// must match to be returned.
+  ///
+  /// If [expectedNumberOfFixesForKind] is non-null, then the number of fixes
+  /// for [kind] is verified to be [expectedNumberOfFixesForKind].
   Future<Fix> _assertHasFix(AnalysisError error,
       {int? expectedNumberOfFixesForKind,
       String? matchFixMessage,
@@ -294,16 +315,7 @@
     var fixes = await _computeFixes(error);
 
     if (expectedNumberOfFixesForKind != null) {
-      var actualNumberOfFixesForKind = 0;
-      for (var fix in fixes) {
-        if (fix.kind == kind) {
-          actualNumberOfFixesForKind++;
-        }
-      }
-      if (actualNumberOfFixesForKind != expectedNumberOfFixesForKind) {
-        fail('Expected $expectedNumberOfFixesForKind fixes of kind $kind,'
-            ' but found $actualNumberOfFixesForKind:\n${fixes.join('\n')}');
-      }
+      _assertNumberOfFixesForKind(fixes, expectedNumberOfFixesForKind);
     }
 
     // If a matchFixMessage was provided,
@@ -364,6 +376,21 @@
     return foundFix;
   }
 
+  /// Computes fixes and verifies that there are [expectedNumberOfFixesForKind]
+  /// fixes for the given [error] of the appropriate kind, and that the messages
+  /// of the fixes are equal to [matchFixMessages].
+  Future<void> _assertHasFixes(
+    AnalysisError error, {
+    required int expectedNumberOfFixesForKind,
+    required List<String> matchFixMessages,
+  }) async {
+    // Compute the fixes for this AnalysisError
+    var fixes = await _computeFixes(error);
+    _assertNumberOfFixesForKind(fixes, expectedNumberOfFixesForKind);
+    var actualFixMessages = [for (var fix in fixes) fix.change.message];
+    expect(actualFixMessages, containsAllInOrder(matchFixMessages));
+  }
+
   Future<void> _assertNoFix(AnalysisError error) async {
     var fixes = await _computeFixes(error);
     for (var fix in fixes) {
@@ -386,6 +413,16 @@
     }
   }
 
+  void _assertNumberOfFixesForKind(
+      List<Fix> fixes, int expectedNumberOfFixesForKind) {
+    var actualNumberOfFixesForKind =
+        fixes.where((fix) => fix.kind == kind).length;
+    if (actualNumberOfFixesForKind != expectedNumberOfFixesForKind) {
+      fail('Expected $expectedNumberOfFixesForKind fixes of kind $kind,'
+          ' but found $actualNumberOfFixesForKind:\n${fixes.join('\n')}');
+    }
+  }
+
   /// Computes fixes for the given [error] in [testUnit].
   Future<List<Fix>> _computeFixes(AnalysisError error) async {
     var analysisContext = contextFor(testFile);
diff --git a/pkg/analysis_server/test/src/services/correction/fix/import_library_project_test.dart b/pkg/analysis_server/test/src/services/correction/fix/import_library_project_test.dart
index fcfed31..98c939b 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/import_library_project_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/import_library_project_test.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analysis_server/src/services/linter/lint_names.dart';
 import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -172,6 +173,12 @@
 ''',
         expectedNumberOfFixesForKind: 2,
         matchFixMessage: "Import library 'a.dart'");
+    await assertHasFixesWithoutApplying(
+        expectedNumberOfFixesForKind: 2,
+        matchFixMessages: [
+          "Import library 'package:test/a.dart'",
+          "Import library 'a.dart'",
+        ]);
   }
 
   Future<void> test_relativeDirective_downOneDirectory() async {
@@ -190,6 +197,29 @@
         matchFixMessage: "Import library 'dir/a.dart'");
   }
 
+  Future<void> test_relativeDirective_preferRelativeImports() async {
+    createAnalysisOptionsFile(lints: [LintNames.prefer_relative_imports]);
+    addSource('/home/test/lib/a.dart', '''
+class Foo {}
+''');
+    await resolveTestCode('''
+main() { new Foo(); }
+''');
+    await assertHasFix('''
+import 'a.dart';
+
+main() { new Foo(); }
+''',
+        expectedNumberOfFixesForKind: 2,
+        matchFixMessage: "Import library 'a.dart'");
+    await assertHasFixesWithoutApplying(
+        expectedNumberOfFixesForKind: 2,
+        matchFixMessages: [
+          "Import library 'a.dart'",
+          "Import library 'package:test/a.dart'",
+        ]);
+  }
+
   Future<void> test_relativeDirective_upOneDirectory() async {
     addSource('/home/test/lib/a.dart', '''
 class Foo {}
diff --git a/pkg/dev_compiler/lib/src/kernel/type_table.dart b/pkg/dev_compiler/lib/src/kernel/type_table.dart
index 15cbd31..b9833db 100644
--- a/pkg/dev_compiler/lib/src/kernel/type_table.dart
+++ b/pkg/dev_compiler/lib/src/kernel/type_table.dart
@@ -43,7 +43,7 @@
 
 /// A name for a type made of JS identifier safe characters.
 ///
-/// 'L' and 'N' are prepended to a type name to represent a legacy or nullable
+/// 'L' and 'N' are appended to a type name to represent a legacy or nullable
 /// flavor of a type.
 String _typeString(DartType type, {bool flat = false}) {
   var nullability = type.declaredNullability == Nullability.legacy
@@ -213,8 +213,10 @@
     // resulting in some duplicated runtime code. We may get some performance
     // wins if we just locally hoist everything.
     if (freeVariables.isNotEmpty) {
+      // TODO(40273) Remove prepended text when we have a better way to hide
+      // these names from debug tools.
       _unboundTypeIds[type] =
-          js_ast.TemporaryId(escapeIdentifier(_typeString(type)));
+          js_ast.TemporaryId(escapeIdentifier('__t\$${_typeString(type)}'));
     }
 
     for (var free in freeVariables) {
diff --git a/pkg/frontend_server/lib/frontend_server.dart b/pkg/frontend_server/lib/frontend_server.dart
index 635cdd2..4b3a9b0 100644
--- a/pkg/frontend_server/lib/frontend_server.dart
+++ b/pkg/frontend_server/lib/frontend_server.dart
@@ -141,6 +141,13 @@
   ..addFlag('track-widget-creation',
       help: 'Run a kernel transformer to track creation locations for widgets.',
       defaultsTo: false)
+  ..addMultiOption(
+    'delete-tostring-package-uri',
+    help: 'Replaces implementations of `toString` with `super.toString()` for '
+        'specified package',
+    valueHelp: 'dart:ui',
+    defaultsTo: const <String>[],
+  )
   ..addFlag('enable-asserts',
       help: 'Whether asserts will be enabled.', defaultsTo: false)
   ..addFlag('sound-null-safety',
@@ -531,6 +538,7 @@
       results = await _runWithPrintRedirection(() => compileToKernel(
           _mainSource, compilerOptions,
           includePlatform: options['link-platform'],
+          deleteToStringPackageUris: options['delete-tostring-package-uri'],
           aot: options['aot'],
           useGlobalTypeFlowAnalysis: options['tfa'],
           environmentDefines: environmentDefines,
diff --git a/pkg/vm/lib/kernel_front_end.dart b/pkg/vm/lib/kernel_front_end.dart
index e0368f7..0458451 100644
--- a/pkg/vm/lib/kernel_front_end.dart
+++ b/pkg/vm/lib/kernel_front_end.dart
@@ -65,6 +65,7 @@
 import 'transformations/unreachable_code_elimination.dart'
     as unreachable_code_elimination;
 import 'transformations/deferred_loading.dart' as deferred_loading;
+import 'transformations/to_string_transformer.dart' as to_string_transformer;
 
 /// Declare options consumed by [runCompiler].
 void declareCompilerOptions(ArgParser args) {
@@ -129,6 +130,13 @@
   args.addFlag('track-widget-creation',
       help: 'Run a kernel transformer to track creation locations for widgets.',
       defaultsTo: false);
+  args.addMultiOption(
+    'delete-tostring-package-uri',
+    help: 'Replaces implementations of `toString` with `super.toString()` for '
+        'specified package',
+    valueHelp: 'dart:ui',
+    defaultsTo: const <String>[],
+  );
   args.addOption('invocation-modes',
       help: 'Provides information to the front end about how it is invoked.',
       defaultsTo: '');
@@ -258,6 +266,7 @@
 
   final results = await compileToKernel(mainUri, compilerOptions,
       includePlatform: additionalDills.isNotEmpty,
+      deleteToStringPackageUris: options['delete-tostring-package-uri'],
       aot: aot,
       useGlobalTypeFlowAnalysis: tfa,
       environmentDefines: environmentDefines,
@@ -324,6 +333,7 @@
 Future<KernelCompilationResults> compileToKernel(
     Uri source, CompilerOptions options,
     {bool includePlatform: false,
+    List<String> deleteToStringPackageUris: const <String>[],
     bool aot: false,
     bool useGlobalTypeFlowAnalysis: false,
     Map<String, String> environmentDefines,
@@ -354,6 +364,11 @@
       compilerResult?.loadedComponents, compilerResult?.sdkComponent,
       includePlatform: includePlatform);
 
+  if (deleteToStringPackageUris.isNotEmpty && component != null) {
+    to_string_transformer.transformComponent(
+        component, deleteToStringPackageUris);
+  }
+
   // Run global transformations only if component is correct.
   if ((aot || minimalKernel) && component != null) {
     await runGlobalTransformations(
diff --git a/pkg/vm/lib/transformations/to_string_transformer.dart b/pkg/vm/lib/transformations/to_string_transformer.dart
new file mode 100644
index 0000000..2931251
--- /dev/null
+++ b/pkg/vm/lib/transformations/to_string_transformer.dart
@@ -0,0 +1,82 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:kernel/ast.dart';
+
+/// Transformer/visitor for toString
+transformComponent(Component component, List<String> packageUris) {
+  component.visitChildren(ToStringVisitor(packageUris.toSet()));
+}
+
+/// A [RecursiveVisitor] that replaces [Object.toString] overrides with
+/// `super.toString()`.
+class ToStringVisitor extends RecursiveVisitor {
+  /// The [packageUris] must not be null.
+  ToStringVisitor(this._packageUris) : assert(_packageUris != null);
+
+  /// A set of package URIs to apply this transformer to, e.g. 'dart:ui' and
+  /// 'package:flutter/foundation.dart'.
+  final Set<String> _packageUris;
+
+  /// Turn 'dart:ui' into 'dart:ui', or
+  /// 'package:flutter/src/semantics_event.dart' into 'package:flutter'.
+  String _importUriToPackage(Uri importUri) =>
+      '${importUri.scheme}:${importUri.pathSegments.first}';
+
+  bool _isInTargetPackage(Procedure node) {
+    return _packageUris
+        .contains(_importUriToPackage(node.enclosingLibrary.importUri));
+  }
+
+  bool _hasKeepAnnotation(Procedure node) {
+    for (ConstantExpression expression
+        in node.annotations.whereType<ConstantExpression>()) {
+      if (expression.constant is! InstanceConstant) {
+        continue;
+      }
+      final InstanceConstant constant = expression.constant as InstanceConstant;
+      final className = constant.classNode.name;
+      final libraryUri =
+          constant.classNode.enclosingLibrary.importUri.toString();
+      if (className == '_KeepToString' && libraryUri == 'dart:ui') {
+        return true;
+      }
+      if (className == 'pragma' && libraryUri == 'dart:core') {
+        for (var fieldRef in constant.fieldValues.keys) {
+          if (fieldRef.asField.name.text == 'name') {
+            Constant name = constant.fieldValues[fieldRef];
+            return name is StringConstant &&
+                name.value == 'flutter:keep-to-string';
+          }
+        }
+        return false;
+      }
+    }
+    return false;
+  }
+
+  @override
+  void visitProcedure(Procedure node) {
+    if (node.name.text == 'toString' &&
+        node.enclosingClass != null &&
+        node.enclosingLibrary != null &&
+        !node.isStatic &&
+        !node.isAbstract &&
+        !node.enclosingClass.isEnum &&
+        _isInTargetPackage(node) &&
+        !_hasKeepAnnotation(node)) {
+      node.function.body.replaceWith(
+        ReturnStatement(
+          SuperMethodInvocation(
+            node.name,
+            Arguments(<Expression>[]),
+          ),
+        ),
+      );
+    }
+  }
+
+  @override
+  void defaultMember(Member node) {}
+}
diff --git a/pkg/vm/test/common_test_utils.dart b/pkg/vm/test/common_test_utils.dart
index 1c97ba4..a45a95d 100644
--- a/pkg/vm/test/common_test_utils.dart
+++ b/pkg/vm/test/common_test_utils.dart
@@ -38,7 +38,8 @@
     {Target target,
     bool enableSuperMixins = false,
     List<String> experimentalFlags,
-    Map<String, String> environmentDefines}) async {
+    Map<String, String> environmentDefines,
+    Uri packagesFileUri}) async {
   final platformKernel =
       computePlatformBinariesLocation().resolve('vm_platform_strong.dill');
   target ??= new TestingVmTarget(new TargetFlags())
@@ -48,6 +49,7 @@
     ..target = target
     ..additionalDills = <Uri>[platformKernel]
     ..environmentDefines = environmentDefines
+    ..packagesFileUri = packagesFileUri
     ..explicitExperimentalFlags =
         parseExperimentalFlags(parseExperimentalArguments(experimentalFlags),
             onError: (String message) {
diff --git a/pkg/vm/test/transformations/to_string_transformer_test.dart b/pkg/vm/test/transformations/to_string_transformer_test.dart
new file mode 100644
index 0000000..ef94ca8
--- /dev/null
+++ b/pkg/vm/test/transformations/to_string_transformer_test.dart
@@ -0,0 +1,42 @@
+// 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:io';
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/kernel.dart';
+import 'package:kernel/verifier.dart';
+import 'package:test/test.dart';
+import 'package:vm/transformations/to_string_transformer.dart'
+    show transformComponent;
+
+import '../common_test_utils.dart';
+
+final Uri pkgVmUri = Platform.script.resolve('../..');
+
+runTestCase(List<String> packageUris, String expectationName) async {
+  final testCasesUri =
+      pkgVmUri.resolve('testcases/transformations/to_string_transformer/');
+  final packagesFileUri =
+      testCasesUri.resolve('.dart_tool/package_config.json');
+  Component component = await compileTestCaseToKernelProgram(
+      Uri.parse('package:to_string_transformer_test/main.dart'),
+      packagesFileUri: packagesFileUri);
+
+  transformComponent(component, packageUris);
+  verifyComponent(component);
+
+  final actual = kernelLibraryToString(component.mainMethod.enclosingLibrary);
+
+  compareResultWithExpectationsFile(
+      testCasesUri.resolve(expectationName), actual);
+}
+
+main() {
+  group('to-string-transformer', () {
+    runTestCase(['package:foo'], 'not_transformed');
+    runTestCase(
+        ['package:foo', 'package:to_string_transformer_test'], 'transformed');
+  });
+}
diff --git a/pkg/vm/testcases/transformations/to_string_transformer/.dart_tool/package_config.json b/pkg/vm/testcases/transformations/to_string_transformer/.dart_tool/package_config.json
new file mode 100644
index 0000000..b8a9fce
--- /dev/null
+++ b/pkg/vm/testcases/transformations/to_string_transformer/.dart_tool/package_config.json
@@ -0,0 +1,11 @@
+{
+    "configVersion": 2,
+    "packages": [
+        {
+            "name": "to_string_transformer_test",
+            "rootUri": "../",
+            "packageUri": "lib",
+            "languageVersion": "2.12"
+          }
+    ]
+}
diff --git a/pkg/vm/testcases/transformations/to_string_transformer/.gitignore b/pkg/vm/testcases/transformations/to_string_transformer/.gitignore
new file mode 100644
index 0000000..0a29033
--- /dev/null
+++ b/pkg/vm/testcases/transformations/to_string_transformer/.gitignore
@@ -0,0 +1 @@
+!.dart_tool
diff --git a/pkg/vm/testcases/transformations/to_string_transformer/lib/main.dart b/pkg/vm/testcases/transformations/to_string_transformer/lib/main.dart
new file mode 100644
index 0000000..96544f2
--- /dev/null
+++ b/pkg/vm/testcases/transformations/to_string_transformer/lib/main.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+
+const keepToString = pragma('flutter:keep-to-string');
+
+String toString() => 'I am static';
+
+abstract class IFoo {
+  @override
+  String toString();
+}
+
+class Foo implements IFoo {
+  @override
+  String toString() => 'I am a Foo';
+}
+
+enum FooEnum { A, B, C }
+
+class Keep {
+  @keepToString
+  @override
+  String toString() => 'I am a Keep';
+}
+
+void main() {
+  final IFoo foo = Foo();
+  print(foo.toString());
+  print(Keep().toString());
+  print(FooEnum.B.toString());
+}
diff --git a/pkg/vm/testcases/transformations/to_string_transformer/not_transformed.expect b/pkg/vm/testcases/transformations/to_string_transformer/not_transformed.expect
new file mode 100644
index 0000000..291ca37
--- /dev/null
+++ b/pkg/vm/testcases/transformations/to_string_transformer/not_transformed.expect
@@ -0,0 +1,68 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+import "dart:convert";
+
+abstract class IFoo extends core::Object {
+  synthetic constructor •() → self::IFoo
+    : super core::Object::•()
+    ;
+  @#C1
+  abstract method toString() → core::String;
+}
+class Foo extends core::Object implements self::IFoo {
+  synthetic constructor •() → self::Foo
+    : super core::Object::•()
+    ;
+  @#C1
+  method toString() → core::String
+    return "I am a Foo";
+}
+class FooEnum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<self::FooEnum> values = #C11;
+  static const field self::FooEnum A = #C4;
+  static const field self::FooEnum B = #C7;
+  static const field self::FooEnum C = #C10;
+  const constructor •(core::int index, core::String _name) → self::FooEnum
+    : self::FooEnum::index = index, self::FooEnum::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{self::FooEnum::_name};
+}
+class Keep extends core::Object {
+  synthetic constructor •() → self::Keep
+    : super core::Object::•()
+    ;
+  @#C14
+  @#C1
+  method toString() → core::String
+    return "I am a Keep";
+}
+static const field core::pragma keepToString = #C14;
+static method toString() → core::String
+  return "I am static";
+static method main() → void {
+  final self::IFoo foo = new self::Foo::•();
+  core::print(foo.{self::IFoo::toString}());
+  core::print(new self::Keep::•().{self::Keep::toString}());
+  core::print((#C7).{self::FooEnum::toString}());
+}
+constants  {
+  #C1 = core::_Override {}
+  #C2 = 0
+  #C3 = "FooEnum.A"
+  #C4 = self::FooEnum {index:#C2, _name:#C3}
+  #C5 = 1
+  #C6 = "FooEnum.B"
+  #C7 = self::FooEnum {index:#C5, _name:#C6}
+  #C8 = 2
+  #C9 = "FooEnum.C"
+  #C10 = self::FooEnum {index:#C8, _name:#C9}
+  #C11 = <self::FooEnum*>[#C4, #C7, #C10]
+  #C12 = "flutter:keep-to-string"
+  #C13 = null
+  #C14 = core::pragma {name:#C12, options:#C13}
+}
diff --git a/pkg/vm/testcases/transformations/to_string_transformer/transformed.expect b/pkg/vm/testcases/transformations/to_string_transformer/transformed.expect
new file mode 100644
index 0000000..7ae1922
--- /dev/null
+++ b/pkg/vm/testcases/transformations/to_string_transformer/transformed.expect
@@ -0,0 +1,68 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+import "dart:convert";
+
+abstract class IFoo extends core::Object {
+  synthetic constructor •() → self::IFoo
+    : super core::Object::•()
+    ;
+  @#C1
+  abstract method toString() → core::String;
+}
+class Foo extends core::Object implements self::IFoo {
+  synthetic constructor •() → self::Foo
+    : super core::Object::•()
+    ;
+  @#C1
+  method toString() → core::String
+    return super.toString();
+}
+class FooEnum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<self::FooEnum> values = #C11;
+  static const field self::FooEnum A = #C4;
+  static const field self::FooEnum B = #C7;
+  static const field self::FooEnum C = #C10;
+  const constructor •(core::int index, core::String _name) → self::FooEnum
+    : self::FooEnum::index = index, self::FooEnum::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{self::FooEnum::_name};
+}
+class Keep extends core::Object {
+  synthetic constructor •() → self::Keep
+    : super core::Object::•()
+    ;
+  @#C14
+  @#C1
+  method toString() → core::String
+    return "I am a Keep";
+}
+static const field core::pragma keepToString = #C14;
+static method toString() → core::String
+  return "I am static";
+static method main() → void {
+  final self::IFoo foo = new self::Foo::•();
+  core::print(foo.{self::IFoo::toString}());
+  core::print(new self::Keep::•().{self::Keep::toString}());
+  core::print((#C7).{self::FooEnum::toString}());
+}
+constants  {
+  #C1 = core::_Override {}
+  #C2 = 0
+  #C3 = "FooEnum.A"
+  #C4 = self::FooEnum {index:#C2, _name:#C3}
+  #C5 = 1
+  #C6 = "FooEnum.B"
+  #C7 = self::FooEnum {index:#C5, _name:#C6}
+  #C8 = 2
+  #C9 = "FooEnum.C"
+  #C10 = self::FooEnum {index:#C8, _name:#C9}
+  #C11 = <self::FooEnum*>[#C4, #C7, #C10]
+  #C12 = "flutter:keep-to-string"
+  #C13 = null
+  #C14 = core::pragma {name:#C12, options:#C13}
+}
diff --git a/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart b/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart
index 212571a..9b74eca 100644
--- a/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart
+++ b/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart
@@ -280,7 +280,7 @@
 
       // Not every byte is accounted for by the snapshot profile, and data and
       // instruction segments are padded to an alignment boundary.
-      final tolerance = 0.03 * actualSize + 2 * segmentAlignment;
+      final tolerance = 0.04 * actualSize + 2 * segmentAlignment;
 
       Expect.approxEquals(
           expectedSize,
diff --git a/runtime/tests/vm/dart_2/v8_snapshot_profile_writer_test.dart b/runtime/tests/vm/dart_2/v8_snapshot_profile_writer_test.dart
index 9b7c95d..23be14f 100644
--- a/runtime/tests/vm/dart_2/v8_snapshot_profile_writer_test.dart
+++ b/runtime/tests/vm/dart_2/v8_snapshot_profile_writer_test.dart
@@ -285,7 +285,7 @@
 
       // Not every byte is accounted for by the snapshot profile, and data and
       // instruction segments are padded to an alignment boundary.
-      final tolerance = 0.03 * actualSize + 2 * segmentAlignment;
+      final tolerance = 0.04 * actualSize + 2 * segmentAlignment;
 
       Expect.approxEquals(
           expectedSize,
diff --git a/runtime/vm/os_macos.cc b/runtime/vm/os_macos.cc
index 4ff4a9b..0a7e529 100644
--- a/runtime/vm/os_macos.cc
+++ b/runtime/vm/os_macos.cc
@@ -106,11 +106,6 @@
 }
 
 int64_t OS::GetCurrentThreadCPUMicros() {
-#if HOST_OS_IOS
-  // Thread CPU time appears unreliable on iOS, sometimes incorrectly reporting
-  // no time elapsed.
-  return -1;
-#else
   mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
   thread_basic_info_data_t info_data;
   thread_basic_info_t info = &info_data;
@@ -124,7 +119,6 @@
   thread_cpu_micros += info->user_time.microseconds;
   thread_cpu_micros += info->system_time.microseconds;
   return thread_cpu_micros;
-#endif
 }
 
 int64_t OS::GetCurrentThreadCPUMicrosForTimeline() {
diff --git a/tools/VERSION b/tools/VERSION
index b247895..220da86 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 129
+PRERELEASE 130
 PRERELEASE_PATCH 0
\ No newline at end of file