Wrap dart:convert to track utf8 decode failures (#26650)

diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 9814318..9f9833f 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import 'package:meta/meta.dart';
 
@@ -18,6 +17,7 @@
 import '../base/process.dart';
 import '../base/process_manager.dart';
 import '../build_info.dart';
+import '../convert.dart';
 import '../device.dart';
 import '../globals.dart';
 import '../project.dart';
@@ -681,7 +681,9 @@
       _timeOrigin = null;
     runCommand(device.adbCommandForDevice(args)).then<void>((Process process) {
       _process = process;
-      const Utf8Decoder decoder = Utf8Decoder(allowMalformed: true);
+      // We expect logcat streams to occasionally contain invalid utf-8,
+      // see: https://github.com/flutter/flutter/pull/8864.
+      const Utf8Decoder decoder = Utf8Decoder(reportErrors: false);
       _process.stdout.transform<String>(decoder).transform<String>(const LineSplitter()).listen(_onLine);
       _process.stderr.transform<String>(decoder).transform<String>(const LineSplitter()).listen(_onLine);
       _process.exitCode.whenComplete(() {
diff --git a/packages/flutter_tools/lib/src/android/android_sdk.dart b/packages/flutter_tools/lib/src/android/android_sdk.dart
index 39f90f5..f97cc5b 100644
--- a/packages/flutter_tools/lib/src/android/android_sdk.dart
+++ b/packages/flutter_tools/lib/src/android/android_sdk.dart
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:convert';
-
 import 'package:meta/meta.dart';
 
 import '../base/common.dart';
@@ -15,6 +13,7 @@
 import '../base/process.dart';
 import '../base/process_manager.dart';
 import '../base/version.dart';
+import '../convert.dart';
 import '../globals.dart';
 import 'android_studio.dart' as android_studio;
 
diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart
index dfbc083..00431f0 100644
--- a/packages/flutter_tools/lib/src/android/android_workflow.dart
+++ b/packages/flutter_tools/lib/src/android/android_workflow.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import '../base/common.dart';
 import '../base/context.dart';
@@ -14,6 +13,7 @@
 import '../base/user_messages.dart';
 import '../base/utils.dart';
 import '../base/version.dart';
+import '../convert.dart';
 import '../doctor.dart';
 import '../globals.dart';
 import 'android_sdk.dart';
@@ -256,13 +256,15 @@
       environment: androidSdk.sdkManagerEnv,
     );
     process.stdin.write('n\n');
+    // We expect logcat streams to occasionally contain invalid utf-8,
+    // see: https://github.com/flutter/flutter/pull/8864.
     final Future<void> output = process.stdout
-      .transform<String>(const Utf8Decoder(allowMalformed: true))
+      .transform<String>(const Utf8Decoder(reportErrors: false))
       .transform<String>(const LineSplitter())
       .listen(_handleLine)
       .asFuture<void>(null);
     final Future<void> errors = process.stderr
-      .transform<String>(const Utf8Decoder(allowMalformed: true))
+      .transform<String>(const Utf8Decoder(reportErrors: false))
       .transform<String>(const LineSplitter())
       .listen(_handleLine)
       .asFuture<void>(null);
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index a13d137..9cc182d 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import 'package:archive/archive.dart';
 import 'package:meta/meta.dart';
@@ -20,6 +19,7 @@
 import '../base/utils.dart';
 import '../build_info.dart';
 import '../cache.dart';
+import '../convert.dart';
 import '../flutter_manifest.dart';
 import '../globals.dart';
 import '../project.dart';
diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart
index c5716af..d97d81b 100644
--- a/packages/flutter_tools/lib/src/asset.dart
+++ b/packages/flutter_tools/lib/src/asset.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import 'package:yaml/yaml.dart';
 
@@ -13,6 +12,7 @@
 import 'base/utils.dart';
 import 'build_info.dart';
 import 'cache.dart';
+import 'convert.dart';
 import 'dart/package_map.dart';
 import 'devfs.dart';
 import 'flutter_manifest.dart';
diff --git a/packages/flutter_tools/lib/src/base/config.dart b/packages/flutter_tools/lib/src/base/config.dart
index 7703326..54c440c 100644
--- a/packages/flutter_tools/lib/src/base/config.dart
+++ b/packages/flutter_tools/lib/src/base/config.dart
@@ -2,8 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:convert';
-
+import '../convert.dart';
 import 'context.dart';
 import 'file_system.dart';
 import 'platform.dart';
diff --git a/packages/flutter_tools/lib/src/base/fingerprint.dart b/packages/flutter_tools/lib/src/base/fingerprint.dart
index b898ed7..79a8333 100644
--- a/packages/flutter_tools/lib/src/base/fingerprint.dart
+++ b/packages/flutter_tools/lib/src/base/fingerprint.dart
@@ -3,12 +3,12 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert' show json;
 
 import 'package:crypto/crypto.dart' show md5;
 import 'package:meta/meta.dart';
 import 'package:quiver/core.dart' show hash2;
 
+import '../convert.dart' show json;
 import '../globals.dart';
 import '../version.dart';
 import 'file_system.dart';
diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart
index 8dca327..40e085e 100644
--- a/packages/flutter_tools/lib/src/base/process.dart
+++ b/packages/flutter_tools/lib/src/base/process.dart
@@ -3,8 +3,8 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
+import '../convert.dart';
 import '../globals.dart';
 import 'file_system.dart';
 import 'io.dart';
diff --git a/packages/flutter_tools/lib/src/base/terminal.dart b/packages/flutter_tools/lib/src/base/terminal.dart
index 6f9d542..de7f012 100644
--- a/packages/flutter_tools/lib/src/base/terminal.dart
+++ b/packages/flutter_tools/lib/src/base/terminal.dart
@@ -3,8 +3,8 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert' show AsciiDecoder;
 
+import '../convert.dart';
 import '../globals.dart';
 import 'context.dart';
 import 'io.dart' as io;
diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart
index ae94dd6..2062631 100644
--- a/packages/flutter_tools/lib/src/base/utils.dart
+++ b/packages/flutter_tools/lib/src/base/utils.dart
@@ -3,12 +3,12 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 import 'dart:math' show Random, max;
 
 import 'package:crypto/crypto.dart';
 import 'package:intl/intl.dart';
 
+import '../convert.dart';
 import '../globals.dart';
 import 'context.dart';
 import 'file_system.dart';
diff --git a/packages/flutter_tools/lib/src/build_runner/build_runner.dart b/packages/flutter_tools/lib/src/build_runner/build_runner.dart
index 7035ddd..3c062cf 100644
--- a/packages/flutter_tools/lib/src/build_runner/build_runner.dart
+++ b/packages/flutter_tools/lib/src/build_runner/build_runner.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import 'package:meta/meta.dart';
 
@@ -14,6 +13,7 @@
 import '../base/platform.dart';
 import '../base/process_manager.dart';
 import '../cache.dart';
+import '../convert.dart';
 import '../dart/package_map.dart';
 import '../globals.dart';
 import '../project.dart';
diff --git a/packages/flutter_tools/lib/src/commands/config.dart b/packages/flutter_tools/lib/src/commands/config.dart
index 2c5bd94..3b2b526 100644
--- a/packages/flutter_tools/lib/src/commands/config.dart
+++ b/packages/flutter_tools/lib/src/commands/config.dart
@@ -3,10 +3,10 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import '../android/android_sdk.dart';
 import '../android/android_studio.dart';
+import '../convert.dart';
 import '../globals.dart';
 import '../runner/flutter_command.dart';
 import '../usage.dart';
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index e20bb5f..ec6f616 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import 'package:linter/src/rules/pub/package_names.dart' as package_names; // ignore: implementation_imports
 import 'package:linter/src/utils.dart' as linter_utils; // ignore: implementation_imports
@@ -18,6 +17,7 @@
 import '../base/os.dart';
 import '../base/utils.dart';
 import '../cache.dart';
+import '../convert.dart';
 import '../dart/pub.dart';
 import '../doctor.dart';
 import '../globals.dart';
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index 79d3071..de597b3 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import 'package:meta/meta.dart';
 
@@ -17,6 +16,7 @@
 import '../base/utils.dart';
 import '../build_info.dart';
 import '../cache.dart';
+import '../convert.dart';
 import '../device.dart';
 import '../emulator.dart';
 import '../fuchsia/fuchsia_device.dart';
diff --git a/packages/flutter_tools/lib/src/commands/screenshot.dart b/packages/flutter_tools/lib/src/commands/screenshot.dart
index 20a2d1f..449a1b8 100644
--- a/packages/flutter_tools/lib/src/commands/screenshot.dart
+++ b/packages/flutter_tools/lib/src/commands/screenshot.dart
@@ -3,11 +3,11 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import '../base/common.dart';
 import '../base/file_system.dart';
 import '../base/utils.dart';
+import '../convert.dart';
 import '../device.dart';
 import '../globals.dart';
 import '../runner/flutter_command.dart';
diff --git a/packages/flutter_tools/lib/src/commands/trace.dart b/packages/flutter_tools/lib/src/commands/trace.dart
index 7956964..96ef949 100644
--- a/packages/flutter_tools/lib/src/commands/trace.dart
+++ b/packages/flutter_tools/lib/src/commands/trace.dart
@@ -3,12 +3,12 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import '../base/common.dart';
 import '../base/file_system.dart';
 import '../base/utils.dart';
 import '../cache.dart';
+import '../convert.dart';
 import '../globals.dart';
 import '../runner/flutter_command.dart';
 import '../tracing.dart';
diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart
index 8439c98..29fa0c6 100644
--- a/packages/flutter_tools/lib/src/compile.dart
+++ b/packages/flutter_tools/lib/src/compile.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import 'package:meta/meta.dart';
 import 'package:usage/uuid/uuid.dart';
@@ -17,6 +16,7 @@
 import 'base/platform.dart';
 import 'base/process_manager.dart';
 import 'base/terminal.dart';
+import 'convert.dart';
 import 'dart/package_map.dart';
 import 'globals.dart';
 
diff --git a/packages/flutter_tools/lib/src/convert.dart b/packages/flutter_tools/lib/src/convert.dart
new file mode 100644
index 0000000..83c8d7d
--- /dev/null
+++ b/packages/flutter_tools/lib/src/convert.dart
@@ -0,0 +1,49 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Hide the original utf8 [Codec] so that we can export our own implementation
+// which adds additional error handling.
+import 'dart:convert' hide utf8;
+import 'dart:convert' as cnv show utf8, Utf8Decoder;
+
+import 'base/common.dart';
+export 'dart:convert' hide utf8, Utf8Codec, Utf8Decoder;
+
+/// A [Codec] which reports malformed bytes when decoding.
+// Created to solve https://github.com/flutter/flutter/issues/15646.
+class Utf8Codec extends Encoding {
+  const Utf8Codec();
+
+  @override
+  Converter<List<int>, String> get decoder => const Utf8Decoder();
+
+  @override
+  Converter<String, List<int>> get encoder => cnv.utf8.encoder;
+
+  @override
+  String get name => cnv.utf8.name;
+}
+
+Encoding get utf8 => const Utf8Codec();
+
+class Utf8Decoder extends cnv.Utf8Decoder {
+  const Utf8Decoder({this.reportErrors = true}) : super(allowMalformed: true);
+
+  final bool reportErrors;
+
+  @override
+  String convert(List<int> codeUnits, [int start = 0, int end]) {
+    final String result = super.convert(codeUnits, start, end);
+    // Finding a unicode replacement character indicates that the input
+    // was malformed.
+    if (reportErrors && result.contains('\u{FFFD}')) {
+      throwToolExit(
+        'Bad UTF-8 encoding found while decoding string: $result. '
+        'The Flutter team would greatly appreciate if you could file a bug or leave a'
+        'comment on the issue https://github.com/flutter/flutter/issues/15646.\n'
+        'The source bytes were:\n$codeUnits\n\n');
+    }
+    return result;
+  }
+}
\ No newline at end of file
diff --git a/packages/flutter_tools/lib/src/dart/analysis.dart b/packages/flutter_tools/lib/src/dart/analysis.dart
index a80aff0..df4b2d1 100644
--- a/packages/flutter_tools/lib/src/dart/analysis.dart
+++ b/packages/flutter_tools/lib/src/dart/analysis.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 import 'dart:math' as math;
 
 import '../base/file_system.dart' hide IOSink;
@@ -13,6 +12,7 @@
 import '../base/process_manager.dart';
 import '../base/terminal.dart';
 import '../base/utils.dart';
+import '../convert.dart';
 import '../globals.dart';
 
 class AnalysisServer {
diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart
index 07adbba..75ebec8 100644
--- a/packages/flutter_tools/lib/src/devfs.dart
+++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -3,8 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert' show base64, utf8;
-
 import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
 import 'package:meta/meta.dart';
 
@@ -15,6 +13,7 @@
 import 'build_info.dart';
 import 'bundle.dart';
 import 'compile.dart';
+import 'convert.dart' show base64, utf8;
 import 'dart/package_map.dart';
 import 'globals.dart';
 import 'vmservice.dart';
diff --git a/packages/flutter_tools/lib/src/flutter_manifest.dart b/packages/flutter_tools/lib/src/flutter_manifest.dart
index bb731d8..50ff01e 100644
--- a/packages/flutter_tools/lib/src/flutter_manifest.dart
+++ b/packages/flutter_tools/lib/src/flutter_manifest.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert' as convert;
 
 import 'package:json_schema/json_schema.dart';
 import 'package:meta/meta.dart';
@@ -12,6 +11,7 @@
 import 'base/file_system.dart';
 import 'base/utils.dart';
 import 'cache.dart';
+import 'convert.dart' as convert;
 import 'globals.dart';
 
 final RegExp _versionPattern = RegExp(r'^(\d+)(\.(\d+)(\.(\d+))?)?(\+(\d+))?$');
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart
index 330835f..ed090c0 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart
@@ -3,13 +3,13 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import '../base/context.dart';
 import '../base/file_system.dart';
 import '../base/io.dart';
 import '../base/process.dart';
 import '../base/process_manager.dart';
+import '../convert.dart';
 import '../globals.dart';
 
 /// The [FuchsiaSdk] instance.
diff --git a/packages/flutter_tools/lib/src/intellij/intellij.dart b/packages/flutter_tools/lib/src/intellij/intellij.dart
index 46278ba..7bed197 100644
--- a/packages/flutter_tools/lib/src/intellij/intellij.dart
+++ b/packages/flutter_tools/lib/src/intellij/intellij.dart
@@ -2,12 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:convert';
-
 import 'package:archive/archive.dart';
 
 import '../base/file_system.dart';
 import '../base/version.dart';
+import '../convert.dart';
 import '../doctor.dart';
 
 class IntelliJPlugins {
diff --git a/packages/flutter_tools/lib/src/ios/code_signing.dart b/packages/flutter_tools/lib/src/ios/code_signing.dart
index f878887..7778654 100644
--- a/packages/flutter_tools/lib/src/ios/code_signing.dart
+++ b/packages/flutter_tools/lib/src/ios/code_signing.dart
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 import 'dart:async';
-import 'dart:convert' show utf8;
 
 import 'package:quiver/strings.dart';
 
@@ -11,6 +10,7 @@
 import '../base/io.dart';
 import '../base/process.dart';
 import '../base/terminal.dart';
+import '../convert.dart' show utf8;
 import '../globals.dart';
 
 /// User message when no development certificates are found in the keychain.
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index b372d6a..d250ea0 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import 'package:meta/meta.dart';
 
@@ -15,6 +14,7 @@
 import '../base/process.dart';
 import '../base/process_manager.dart';
 import '../build_info.dart';
+import '../convert.dart';
 import '../device.dart';
 import '../globals.dart';
 import '../protocol_discovery.dart';
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index bcb24be..dd87a5e 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert' show json;
 
 import 'package:meta/meta.dart';
 
@@ -20,6 +19,7 @@
 import '../base/process_manager.dart';
 import '../base/utils.dart';
 import '../build_info.dart';
+import '../convert.dart';
 import '../globals.dart';
 import '../plugins.dart';
 import '../project.dart';
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 69c8a1c..2ac62f8 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 import 'dart:math' as math;
 
 import '../application_package.dart';
@@ -17,6 +16,7 @@
 import '../base/utils.dart';
 import '../build_info.dart';
 import '../bundle.dart' as bundle;
+import '../convert.dart';
 import '../device.dart';
 import '../globals.dart';
 import '../protocol_discovery.dart';
diff --git a/packages/flutter_tools/lib/src/macos/macos_device.dart b/packages/flutter_tools/lib/src/macos/macos_device.dart
index 529226f..7ba605b 100644
--- a/packages/flutter_tools/lib/src/macos/macos_device.dart
+++ b/packages/flutter_tools/lib/src/macos/macos_device.dart
@@ -2,14 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:convert';
-
 import '../application_package.dart';
 import '../base/io.dart';
 import '../base/os.dart';
 import '../base/platform.dart';
 import '../base/process_manager.dart';
 import '../build_info.dart';
+import '../convert.dart';
 import '../device.dart';
 import '../globals.dart';
 import '../macos/application_package.dart';
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index c5f1039..df9627e 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import 'package:json_rpc_2/error_code.dart' as rpc_error_code;
 import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
@@ -17,6 +16,7 @@
 import 'base/utils.dart';
 import 'build_info.dart';
 import 'compile.dart';
+import 'convert.dart';
 import 'dart/dependencies.dart';
 import 'dart/pub.dart';
 import 'devfs.dart';
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
index 68cc71e..04d654e 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import 'package:args/args.dart';
 import 'package:args/command_runner.dart';
@@ -27,6 +26,7 @@
 import '../base/user_messages.dart';
 import '../base/utils.dart';
 import '../cache.dart';
+import '../convert.dart';
 import '../dart/package_map.dart';
 import '../device.dart';
 import '../globals.dart';
diff --git a/packages/flutter_tools/lib/src/services.dart b/packages/flutter_tools/lib/src/services.dart
index 934f05e..fee5383 100644
--- a/packages/flutter_tools/lib/src/services.dart
+++ b/packages/flutter_tools/lib/src/services.dart
@@ -3,12 +3,12 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import 'package:yaml/yaml.dart';
 
 import 'android/android_sdk.dart';
 import 'base/file_system.dart';
+import 'convert.dart';
 import 'dart/package_map.dart';
 import 'globals.dart';
 
diff --git a/packages/flutter_tools/lib/src/test/event_printer.dart b/packages/flutter_tools/lib/src/test/event_printer.dart
index ab2648f..e672f20 100644
--- a/packages/flutter_tools/lib/src/test/event_printer.dart
+++ b/packages/flutter_tools/lib/src/test/event_printer.dart
@@ -2,9 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:convert' show json;
-
 import '../base/io.dart' show stdout;
+import '../convert.dart';
 import 'watcher.dart';
 
 /// Prints JSON events when running a test in --machine mode.
diff --git a/packages/flutter_tools/lib/src/test/flutter_platform.dart b/packages/flutter_tools/lib/src/test/flutter_platform.dart
index 5886f03..70bbf52 100644
--- a/packages/flutter_tools/lib/src/test/flutter_platform.dart
+++ b/packages/flutter_tools/lib/src/test/flutter_platform.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import 'package:meta/meta.dart';
 import 'package:stream_channel/stream_channel.dart';
@@ -26,6 +25,7 @@
 import '../build_info.dart';
 import '../bundle.dart';
 import '../compile.dart';
+import '../convert.dart';
 import '../dart/package_map.dart';
 import '../globals.dart';
 import '../vmservice.dart';
@@ -144,7 +144,7 @@
   final StringBuffer buffer = StringBuffer();
   buffer.write('''
 import 'dart:async';
-import 'dart:convert';
+import 'dart:convert';  // ignore: dart_convert_import
 import 'dart:io';  // ignore: dart_io_import
 import 'dart:isolate';
 
diff --git a/packages/flutter_tools/lib/src/tester/flutter_tester.dart b/packages/flutter_tools/lib/src/tester/flutter_tester.dart
index f3e2092..de29600 100644
--- a/packages/flutter_tools/lib/src/tester/flutter_tester.dart
+++ b/packages/flutter_tools/lib/src/tester/flutter_tester.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import 'package:meta/meta.dart';
 
@@ -15,6 +14,7 @@
 import '../base/process_manager.dart';
 import '../build_info.dart';
 import '../bundle.dart' as bundle;
+import '../convert.dart';
 import '../dart/package_map.dart';
 import '../device.dart';
 import '../globals.dart';
diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart
index 9e3cb56..29dbc5e 100644
--- a/packages/flutter_tools/lib/src/version.dart
+++ b/packages/flutter_tools/lib/src/version.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import 'package:meta/meta.dart';
 
@@ -15,6 +14,7 @@
 import 'base/process_manager.dart';
 import 'base/time.dart';
 import 'cache.dart';
+import 'convert.dart';
 import 'globals.dart';
 
 class FlutterVersion {
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart
index 7a72740..c733b52 100644
--- a/packages/flutter_tools/lib/src/vmservice.dart
+++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert' show base64;
 import 'dart:math' as math;
 
 import 'package:file/file.dart';
@@ -19,6 +18,7 @@
 import 'base/file_system.dart';
 import 'base/io.dart' as io;
 import 'base/utils.dart';
+import 'convert.dart' show base64;
 import 'globals.dart';
 import 'vmservice_record_replay.dart';
 
diff --git a/packages/flutter_tools/lib/src/vmservice_record_replay.dart b/packages/flutter_tools/lib/src/vmservice_record_replay.dart
index 4749b58..0061fd4 100644
--- a/packages/flutter_tools/lib/src/vmservice_record_replay.dart
+++ b/packages/flutter_tools/lib/src/vmservice_record_replay.dart
@@ -3,13 +3,13 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 
 import 'package:file/file.dart';
 import 'package:stream_channel/stream_channel.dart';
 
 import 'base/io.dart';
 import 'base/process.dart';
+import 'convert.dart';
 import 'globals.dart';
 
 const String _kManifest = 'MANIFEST.txt';
diff --git a/packages/flutter_tools/lib/src/vscode/vscode.dart b/packages/flutter_tools/lib/src/vscode/vscode.dart
index 00dda29..c9503fd 100644
--- a/packages/flutter_tools/lib/src/vscode/vscode.dart
+++ b/packages/flutter_tools/lib/src/vscode/vscode.dart
@@ -2,12 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:convert';
-
 import '../base/common.dart';
 import '../base/file_system.dart';
 import '../base/platform.dart';
 import '../base/version.dart';
+import '../convert.dart';
 import '../doctor.dart';
 
 // Include VS Code insiders (useful for debugging).
diff --git a/packages/flutter_tools/test/forbidden_imports_test.dart b/packages/flutter_tools/test/forbidden_imports_test.dart
index cf3b387..06b1fc6 100644
--- a/packages/flutter_tools/test/forbidden_imports_test.dart
+++ b/packages/flutter_tools/test/forbidden_imports_test.dart
@@ -47,6 +47,28 @@
       }
     }
   });
+
+  test('no unauthorized imports of dart:convert', () {
+    final String whitelistedPath = fs.path.join(flutterTools, 'lib', 'src', 'convert.dart');
+    bool _isNotWhitelisted(FileSystemEntity entity) => entity.path != whitelistedPath;
+
+    for (String dirName in <String>['lib']) {
+      final Iterable<File> files = fs.directory(fs.path.join(flutterTools, dirName))
+        .listSync(recursive: true)
+        .where(_isDartFile)
+        .where(_isNotWhitelisted)
+        .map(_asFile);
+      for (File file in files) {
+        for (String line in file.readAsLinesSync()) {
+          if (line.startsWith(RegExp(r'import.*dart:convert')) &&
+              !line.contains('ignore: dart_convert_import')) {
+            final String relativePath = fs.path.relative(file.path, from:flutterTools);
+            fail("$relativePath imports 'dart:convert'; import 'lib/src/convert.dart' instead");
+          }
+        }
+      }
+    }
+  });
 }
 
 bool _isDartFile(FileSystemEntity entity) => entity is File && entity.path.endsWith('.dart');