[flutter_tools] add --null-assertions flag for debugging with null safety (#63416)

diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 652ba17..262636c 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -626,9 +626,8 @@
       );
     }
 
-    List<String> cmd;
-
-    cmd = <String>[
+    final String dartVmFlags = computeDartVmFlags(debuggingOptions);
+    final List<String> cmd = <String>[
       'shell', 'am', 'start',
       '-a', 'android.intent.action.RUN',
       '-f', '0x20000000', // FLAG_ACTIVITY_SINGLE_TOP
@@ -665,8 +664,8 @@
           ...<String>['--ez', 'start-paused', 'true'],
         if (debuggingOptions.disableServiceAuthCodes)
           ...<String>['--ez', 'disable-service-auth-codes', 'true'],
-        if (debuggingOptions.dartFlags.isNotEmpty)
-          ...<String>['--es', 'dart-flags', debuggingOptions.dartFlags],
+        if (dartVmFlags.isNotEmpty)
+          ...<String>['--es', 'dart-flags', dartVmFlags],
         if (debuggingOptions.useTestFonts)
           ...<String>['--ez', 'use-test-fonts', 'true'],
         if (debuggingOptions.verboseSystemLogs)
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index 403205e..fd5b739 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -423,6 +423,7 @@
         fastStart: boolArg('fast-start')
           && !runningWithPrebuiltApplication
           && devices.every((Device device) => device.supportsFastStart),
+        nullAssertions: boolArg('null-assertions'),
       );
     }
   }
diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart
index 077cae8..a990b39 100644
--- a/packages/flutter_tools/lib/src/commands/test.dart
+++ b/packages/flutter_tools/lib/src/commands/test.dart
@@ -265,6 +265,7 @@
       web: stringArg('platform') == 'chrome',
       randomSeed: stringArg('test-randomize-ordering-seed'),
       extraFrontEndOptions: getBuildInfo(forcedBuildMode: BuildMode.debug).extraFrontEndOptions,
+      nullAssertions: boolArg(FlutterOptions.kNullAssertions),
     );
 
     if (collector != null) {
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index c691610..2a16f8e 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -790,6 +790,7 @@
     this.webEnableExpressionEvaluation = false,
     this.vmserviceOutFile,
     this.fastStart = false,
+    this.nullAssertions = false,
    }) : debuggingEnabled = true;
 
   DebuggingOptions.disabled(this.buildInfo, {
@@ -821,7 +822,8 @@
       deviceVmServicePort = null,
       vmserviceOutFile = null,
       fastStart = false,
-      webEnableExpressionEvaluation = false;
+      webEnableExpressionEvaluation = false,
+      nullAssertions = false;
 
   final bool debuggingEnabled;
 
@@ -868,6 +870,8 @@
   final String vmserviceOutFile;
   final bool fastStart;
 
+  final bool nullAssertions;
+
   bool get hasObservatoryPort => hostVmServicePort != null;
 }
 
@@ -993,3 +997,14 @@
   @override
   Future<void> dispose() async { }
 }
+
+/// Append --null_assertions to any existing Dart VM flags if
+/// [debuggingOptions.nullAssertions] is true.
+String computeDartVmFlags(DebuggingOptions debuggingOptions) {
+  return <String>[
+    if (debuggingOptions.dartFlags?.isNotEmpty ?? false)
+      debuggingOptions.dartFlags,
+    if (debuggingOptions.nullAssertions)
+      '--null_assertions',
+  ].join(',');
+}
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index 056c857..d8e7c74 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -355,6 +355,7 @@
       ?? math.Random(packageId.hashCode).nextInt(16383) + 49152;
 
     // Step 3: Attempt to install the application on the device.
+    final String dartVmFlags = computeDartVmFlags(debuggingOptions);
     final List<String> launchArguments = <String>[
       '--enable-dart-profiling',
       // These arguments are required to support the fallback connection strategy
@@ -363,7 +364,7 @@
       '--disable-service-auth-codes',
       '--observatory-port=$assumedObservatoryPort',
       if (debuggingOptions.startPaused) '--start-paused',
-      if (debuggingOptions.dartFlags.isNotEmpty) '--dart-flags="${debuggingOptions.dartFlags}"',
+      if (dartVmFlags.isNotEmpty) '--dart-flags="$dartVmFlags"',
       if (debuggingOptions.useTestFonts) '--use-test-fonts',
       // "--enable-checked-mode" and "--verify-entry-points" should always be
       // passed when we launch debug build via "ios-deploy". However, we don't
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 43aa7a7..dcba2bb 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -411,6 +411,7 @@
     }
 
     // Prepare launch arguments.
+    final String dartVmFlags = computeDartVmFlags(debuggingOptions);
     final List<String> args = <String>[
       '--enable-dart-profiling',
       if (debuggingOptions.debuggingEnabled) ...<String>[
@@ -423,6 +424,7 @@
         if (debuggingOptions.skiaDeterministicRendering) '--skia-deterministic-rendering',
         if (debuggingOptions.useTestFonts) '--use-test-fonts',
         if (debuggingOptions.traceAllowlist != null) '--trace-allowlist="${debuggingOptions.traceAllowlist}"',
+        if (dartVmFlags.isNotEmpty) '--dart-flags=$dartVmFlags'
         '--observatory-port=${debuggingOptions.hostVmServicePort ?? 0}',
       ],
     ];
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 404b74e..6791a8c 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -113,6 +113,7 @@
   static const String kNullSafety = 'sound-null-safety';
   static const String kDeviceUser = 'device-user';
   static const String kAnalyzeSize = 'analyze-size';
+  static const String kNullAssertions = 'null-assertions';
 }
 
 abstract class FlutterCommand extends Command<void> {
@@ -500,6 +501,12 @@
       defaultsTo: null,
       hide: hide,
     );
+    argParser.addFlag(FlutterOptions.kNullAssertions,
+      help:
+        'Perform additional null assertions on the boundaries of migrated and '
+        'unmigrated code. This setting is not currently supported on desktop '
+        'devices.'
+    );
   }
 
   void usesExtraFrontendOptions() {
diff --git a/packages/flutter_tools/lib/src/test/flutter_platform.dart b/packages/flutter_tools/lib/src/test/flutter_platform.dart
index a6127c4..686ccaa 100644
--- a/packages/flutter_tools/lib/src/test/flutter_platform.dart
+++ b/packages/flutter_tools/lib/src/test/flutter_platform.dart
@@ -92,6 +92,7 @@
   List<String> extraFrontEndOptions,
   // Deprecated, use extraFrontEndOptions.
   List<String> dartExperiments,
+  bool nullAssertions = false,
 }) {
   assert(testWrapper != null);
   assert(enableObservatory || (!startPaused && observatoryPort == null));
@@ -125,6 +126,7 @@
     flutterProject: flutterProject,
     icudtlPath: icudtlPath,
     extraFrontEndOptions: extraFrontEndOptions,
+    nullAssertions: nullAssertions,
   );
   platformPluginRegistration(platform);
   return platform;
@@ -268,6 +270,7 @@
     this.projectRootDirectory,
     this.flutterProject,
     this.icudtlPath,
+    this.nullAssertions = false,
     @required this.extraFrontEndOptions,
   }) : assert(shellPath != null);
 
@@ -290,6 +293,7 @@
   final FlutterProject flutterProject;
   final String icudtlPath;
   final List<String> extraFrontEndOptions;
+  final bool nullAssertions;
 
   Directory fontsDirectory;
 
@@ -844,6 +848,8 @@
       '--non-interactive',
       '--use-test-fonts',
       '--packages=$packages',
+      if (nullAssertions)
+        '--dart-flags=--null_assertions',
       testPath,
     ];
 
diff --git a/packages/flutter_tools/lib/src/test/runner.dart b/packages/flutter_tools/lib/src/test/runner.dart
index 3f3c951..daa1cff 100644
--- a/packages/flutter_tools/lib/src/test/runner.dart
+++ b/packages/flutter_tools/lib/src/test/runner.dart
@@ -52,6 +52,7 @@
     bool web = false,
     String randomSeed,
     @required List<String> extraFrontEndOptions,
+    bool nullAssertions = false,
   });
 }
 
@@ -86,6 +87,7 @@
     bool web = false,
     String randomSeed,
     @required List<String> extraFrontEndOptions,
+    bool nullAssertions = false,
   }) async {
     // Configure package:test to use the Flutter engine for child processes.
     final String shellPath = globals.artifacts.getArtifactPath(Artifact.flutterTester);
@@ -178,6 +180,7 @@
       flutterProject: flutterProject,
       icudtlPath: icudtlPath,
       extraFrontEndOptions: extraFrontEndOptions,
+      nullAssertions: nullAssertions,
     );
 
     // Make the global packages path absolute.