diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index 652ba177286..262636cb2e3 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 @@ class AndroidDevice extends Device { ); } - List cmd; - - cmd = [ + final String dartVmFlags = computeDartVmFlags(debuggingOptions); + final List cmd = [ 'shell', 'am', 'start', '-a', 'android.intent.action.RUN', '-f', '0x20000000', // FLAG_ACTIVITY_SINGLE_TOP @@ -665,8 +664,8 @@ class AndroidDevice extends Device { ...['--ez', 'start-paused', 'true'], if (debuggingOptions.disableServiceAuthCodes) ...['--ez', 'disable-service-auth-codes', 'true'], - if (debuggingOptions.dartFlags.isNotEmpty) - ...['--es', 'dart-flags', debuggingOptions.dartFlags], + if (dartVmFlags.isNotEmpty) + ...['--es', 'dart-flags', dartVmFlags], if (debuggingOptions.useTestFonts) ...['--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 403205e267b..fd5b7390680 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -423,6 +423,7 @@ class RunCommand extends RunCommandBase { 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 077cae8c2e5..a990b398883 100644 --- a/packages/flutter_tools/lib/src/commands/test.dart +++ b/packages/flutter_tools/lib/src/commands/test.dart @@ -265,6 +265,7 @@ class TestCommand extends FlutterCommand { 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 c69161055e8..2a16f8ee281 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -790,6 +790,7 @@ class DebuggingOptions { this.webEnableExpressionEvaluation = false, this.vmserviceOutFile, this.fastStart = false, + this.nullAssertions = false, }) : debuggingEnabled = true; DebuggingOptions.disabled(this.buildInfo, { @@ -821,7 +822,8 @@ class DebuggingOptions { deviceVmServicePort = null, vmserviceOutFile = null, fastStart = false, - webEnableExpressionEvaluation = false; + webEnableExpressionEvaluation = false, + nullAssertions = false; final bool debuggingEnabled; @@ -868,6 +870,8 @@ class DebuggingOptions { final String vmserviceOutFile; final bool fastStart; + final bool nullAssertions; + bool get hasObservatoryPort => hostVmServicePort != null; } @@ -993,3 +997,14 @@ class NoOpDevicePortForwarder implements DevicePortForwarder { @override Future dispose() async { } } + +/// Append --null_assertions to any existing Dart VM flags if +/// [debuggingOptions.nullAssertions] is true. +String computeDartVmFlags(DebuggingOptions debuggingOptions) { + return [ + 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 056c857f681..d8e7c7412bb 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -355,6 +355,7 @@ class IOSDevice extends Device { ?? math.Random(packageId.hashCode).nextInt(16383) + 49152; // Step 3: Attempt to install the application on the device. + final String dartVmFlags = computeDartVmFlags(debuggingOptions); final List launchArguments = [ '--enable-dart-profiling', // These arguments are required to support the fallback connection strategy @@ -363,7 +364,7 @@ class IOSDevice extends Device { '--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 43aa7a73955..dcba2bbbc29 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart @@ -411,6 +411,7 @@ class IOSSimulator extends Device { } // Prepare launch arguments. + final String dartVmFlags = computeDartVmFlags(debuggingOptions); final List args = [ '--enable-dart-profiling', if (debuggingOptions.debuggingEnabled) ...[ @@ -423,6 +424,7 @@ class IOSSimulator extends Device { 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 404b74e6166..6791a8c9631 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 @@ class FlutterOptions { 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 { @@ -500,6 +501,12 @@ abstract class FlutterCommand extends Command { 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 a6127c4e109..686ccaa88a4 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 @@ FlutterPlatform installHook({ List extraFrontEndOptions, // Deprecated, use extraFrontEndOptions. List dartExperiments, + bool nullAssertions = false, }) { assert(testWrapper != null); assert(enableObservatory || (!startPaused && observatoryPort == null)); @@ -125,6 +126,7 @@ FlutterPlatform installHook({ flutterProject: flutterProject, icudtlPath: icudtlPath, extraFrontEndOptions: extraFrontEndOptions, + nullAssertions: nullAssertions, ); platformPluginRegistration(platform); return platform; @@ -268,6 +270,7 @@ class FlutterPlatform extends PlatformPlugin { this.projectRootDirectory, this.flutterProject, this.icudtlPath, + this.nullAssertions = false, @required this.extraFrontEndOptions, }) : assert(shellPath != null); @@ -290,6 +293,7 @@ class FlutterPlatform extends PlatformPlugin { final FlutterProject flutterProject; final String icudtlPath; final List extraFrontEndOptions; + final bool nullAssertions; Directory fontsDirectory; @@ -844,6 +848,8 @@ class FlutterPlatform extends PlatformPlugin { '--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 3f3c951c08e..daa1cff67ee 100644 --- a/packages/flutter_tools/lib/src/test/runner.dart +++ b/packages/flutter_tools/lib/src/test/runner.dart @@ -52,6 +52,7 @@ abstract class FlutterTestRunner { bool web = false, String randomSeed, @required List extraFrontEndOptions, + bool nullAssertions = false, }); } @@ -86,6 +87,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner { bool web = false, String randomSeed, @required List 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 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner { flutterProject: flutterProject, icudtlPath: icudtlPath, extraFrontEndOptions: extraFrontEndOptions, + nullAssertions: nullAssertions, ); // Make the global packages path absolute. diff --git a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart index 7d4b64360b4..83030d9a5e6 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart @@ -185,6 +185,7 @@ class FakeFlutterTestRunner implements FlutterTestRunner { bool web = false, String randomSeed, @override List extraFrontEndOptions, + bool nullAssertions = false, }) async { lastEnableObservatoryValue = enableObservatory; return exitCode; diff --git a/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart b/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart index 7218cac2f0a..75dcc477dcd 100644 --- a/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart @@ -254,7 +254,7 @@ void main() { '--ez', 'verify-entry-points', 'true', '--ez', 'start-paused', 'true', '--ez', 'disable-service-auth-codes', 'true', - '--es', 'dart-flags', 'foo', + '--es', 'dart-flags', 'foo,--null_assertions', '--ez', 'use-test-fonts', 'true', '--ez', 'verbose-logging', 'true', '--user', '10', @@ -281,6 +281,7 @@ void main() { purgePersistentCache: true, useTestFonts: true, verboseSystemLogs: true, + nullAssertions: true, ), platformArgs: {}, userIdentifier: '10', diff --git a/packages/flutter_tools/test/general.shard/device_test.dart b/packages/flutter_tools/test/general.shard/device_test.dart index 1c1b3afd15c..448177572d0 100644 --- a/packages/flutter_tools/test/general.shard/device_test.dart +++ b/packages/flutter_tools/test/general.shard/device_test.dart @@ -416,6 +416,13 @@ void main() { ); }); }); + + testWithoutContext('computeDartVmFlags handles various combinations of Dart VM flags and null_assertions', () { + expect(computeDartVmFlags(DebuggingOptions.enabled(BuildInfo.debug, dartFlags: null)), ''); + expect(computeDartVmFlags(DebuggingOptions.enabled(BuildInfo.debug, dartFlags: '--foo')), '--foo'); + expect(computeDartVmFlags(DebuggingOptions.enabled(BuildInfo.debug, dartFlags: '', nullAssertions: true)), '--null_assertions'); + expect(computeDartVmFlags(DebuggingOptions.enabled(BuildInfo.debug, dartFlags: '--foo', nullAssertions: true)), '--foo,--null_assertions'); + }); } class TestDeviceManager extends DeviceManager { diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart index 9b56f45f769..561ed850398 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart @@ -319,7 +319,7 @@ void main() { '--disable-service-auth-codes', '--observatory-port=60700', '--start-paused', - '--dart-flags="--foo"', + '--dart-flags="--foo,--null_assertions"', '--enable-checked-mode', '--verify-entry-points', '--enable-software-rendering', @@ -385,6 +385,7 @@ void main() { cacheSkSL: true, purgePersistentCache: true, verboseSystemLogs: true, + nullAssertions: true, ), platformArgs: {}, fallbackPollingDelay: Duration.zero,