[flutter_tools] wire up native-null-assertions for flutter web (#71618)
diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart index aff6c7c..ee56688d 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/web.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart
@@ -39,6 +39,9 @@ /// Whether the dart2js build should output source maps. const String kSourceMapsEnabled = 'SourceMaps'; +/// Whether the dart2js native null assertions are enabled. +const String kNativeNullAssertions = 'NativeNullAssertions'; + /// The caching strategy for the generated service worker. enum ServiceWorkerStrategy { /// Download the app shell eagerly and all other assets lazily. @@ -190,6 +193,7 @@ Future<void> build(Environment environment) async { final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); final bool sourceMapsEnabled = environment.defines[kSourceMapsEnabled] == 'true'; + final bool nativeNullAssertions = environment.defines[kNativeNullAssertions] == 'true'; final List<String> sharedCommandOptions = <String>[ globals.artifacts.getArtifactPath(Artifact.engineDartBinary), @@ -197,6 +201,8 @@ globals.artifacts.getArtifactPath(Artifact.dart2jsSnapshot), '--libraries-spec=${globals.fs.path.join(globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), 'libraries.json')}', ...?decodeDartDefines(environment.defines, kExtraFrontEndOptions), + if (nativeNullAssertions) + '--native-null-assertions', if (buildMode == BuildMode.profile) '-Ddart.vm.profile=true' else
diff --git a/packages/flutter_tools/lib/src/commands/build_web.dart b/packages/flutter_tools/lib/src/commands/build_web.dart index 62fad31..05c6ba5 100644 --- a/packages/flutter_tools/lib/src/commands/build_web.dart +++ b/packages/flutter_tools/lib/src/commands/build_web.dart
@@ -26,6 +26,7 @@ usesWebRendererOption(); addEnableExperimentation(hide: !verboseHelp); addNullSafetyModeOptions(hide: !verboseHelp); + addNativeNullAssertions(hide: false); argParser.addFlag('csp', defaultsTo: false, negatable: false, @@ -90,6 +91,7 @@ boolArg('csp'), stringArg('pwa-strategy'), boolArg('source-maps'), + boolArg('native-null-assertions'), ); return FlutterCommandResult.success(); }
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index d18cc18..374a554 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -30,6 +30,7 @@ usesDartDefineOption(); usesFlavorOption(); usesWebRendererOption(); + addNativeNullAssertions(hide: !verboseHelp); argParser ..addFlag('trace-startup', negatable: false, @@ -205,6 +206,7 @@ && boolArg('fast-start') && !runningWithPrebuiltApplication, nullAssertions: boolArg('null-assertions'), + nativeNullAssertions: boolArg('native-null-assertions'), ); } }
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 53932d7..019c129 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart
@@ -863,6 +863,7 @@ this.vmserviceOutFile, this.fastStart = false, this.nullAssertions = false, + this.nativeNullAssertions = false, }) : debuggingEnabled = true; DebuggingOptions.disabled(this.buildInfo, { @@ -897,7 +898,8 @@ vmserviceOutFile = null, fastStart = false, webEnableExpressionEvaluation = false, - nullAssertions = false; + nullAssertions = false, + nativeNullAssertions = false; final bool debuggingEnabled; @@ -947,6 +949,12 @@ final bool nullAssertions; + /// Additional null runtime checks inserted for web applications. + /// + /// See also: + /// * https://github.com/dart-lang/sdk/blob/master/sdk/lib/html/doc/NATIVE_NULL_ASSERTIONS.md + final bool nativeNullAssertions; + bool get hasObservatoryPort => hostVmServicePort != null; }
diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart index 2c24a79..7ca818b 100644 --- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart +++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart
@@ -605,6 +605,7 @@ @required this.expressionCompiler, @required this.chromiumLauncher, @required this.nullAssertions, + @required this.nativeNullAssertions, @required this.nullSafetyMode, this.testMode = false, }) : _port = port; @@ -621,6 +622,7 @@ final ExpressionCompiler expressionCompiler; final ChromiumLauncher chromiumLauncher; final bool nullAssertions; + final bool nativeNullAssertions; final int _port; final NullSafetyMode nullSafetyMode; @@ -779,6 +781,7 @@ generateMainModule( entrypoint: entrypoint, nullAssertions: nullAssertions, + nativeNullAssertions: nativeNullAssertions, ), ); // TODO(jonahwilliams): refactor the asset code in this and the regular devfs to
diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index e67be35..f0c98f7 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
@@ -501,6 +501,7 @@ chromiumLauncher: _chromiumLauncher, nullAssertions: debuggingOptions.nullAssertions, nullSafetyMode: debuggingOptions.buildInfo.nullSafetyMode, + nativeNullAssertions: debuggingOptions.nativeNullAssertions, ); final Uri url = await device.devFS.create(); if (debuggingOptions.buildInfo.isDebug) { @@ -520,6 +521,7 @@ false, kNoneWorker, true, + debuggingOptions.nativeNullAssertions, ); } await device.device.startApp( @@ -587,6 +589,7 @@ false, kNoneWorker, true, + debuggingOptions.nativeNullAssertions, ); } on ToolExit { return OperationResult(1, 'Failed to recompile application.');
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index e2d2e35..f0ba9b8 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -674,6 +674,19 @@ ); } + void addNativeNullAssertions({ bool hide = false }) { + argParser.addFlag('native-null-assertions', + defaultsTo: true, + hide: hide, + help: 'Enables additional runtime null checks in web applications to ensure ' + 'the correct nullability of native (such as in dart:html) and external ' + '(such as with JS interop) types. This is enabled by default but only takes ' + 'effect in sound mode. To report an issue with a null assertion failure in ' + 'dart:html or the other dart web libraries, please file a bug at ' + 'https://github.com/dart-lang/sdk/issues/labels/web-libraries .' + ); + } + /// Adds build options common to all of the desktop build commands. void addCommonDesktopBuildOptions({ bool verboseHelp = false }) { addBuildModeFlags(verboseHelp: verboseHelp);
diff --git a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart index 4eb4aa0..2b230f1 100644 --- a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart
@@ -224,6 +224,7 @@ final String generatedFile = _fileSystem.path.split(leadingPath).join('_') + '.dart.test.dart.js'; return shelf.Response.ok(generateMainModule( nullAssertions: nullAssertions, + nativeNullAssertions: true, bootstrapModule: _fileSystem.path.basename(leadingPath) + '.dart.bootstrap', entrypoint: '/' + generatedFile ), headers: <String, String>{
diff --git a/packages/flutter_tools/lib/src/web/bootstrap.dart b/packages/flutter_tools/lib/src/web/bootstrap.dart index 18c85a3..6071a27 100644 --- a/packages/flutter_tools/lib/src/web/bootstrap.dart +++ b/packages/flutter_tools/lib/src/web/bootstrap.dart
@@ -54,6 +54,7 @@ String generateMainModule({ @required String entrypoint, @required bool nullAssertions, + @required bool nativeNullAssertions, String bootstrapModule = 'main_module.bootstrap', }) { // TODO(jonahwilliams): fix typo in dwds and update. @@ -63,9 +64,8 @@ define("$bootstrapModule", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) { dart_sdk.dart.setStartAsyncSynchronously(true); dart_sdk._debugger.registerDevtoolsFormatter(); - if ($nullAssertions) { - dart_sdk.dart.nonNullAsserts(true); - } + dart_sdk.dart.nonNullAsserts($nullAssertions); + dart_sdk.dart.nativeNonNullAsserts($nativeNullAssertions); // See the generateMainModule doc comment. var child = {};
diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart index 7e08363..160f41d 100644 --- a/packages/flutter_tools/lib/src/web/compile.dart +++ b/packages/flutter_tools/lib/src/web/compile.dart
@@ -24,6 +24,7 @@ bool csp, String serviceWorkerStrategy, bool sourceMaps, + bool nativeNullAssertions, ) async { if (!flutterProject.web.existsSync()) { throwToolExit('Missing index.html.'); @@ -51,6 +52,7 @@ kCspMode: csp.toString(), kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(), kSourceMapsEnabled: sourceMaps.toString(), + kNativeNullAssertions: nativeNullAssertions.toString(), if (serviceWorkerStrategy != null) kServiceWorkerStrategy: serviceWorkerStrategy, if (buildInfo.extraFrontEndOptions?.isNotEmpty ?? false)
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart index 4a76cf5..ec9a0bf 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart
@@ -56,6 +56,7 @@ false, null, true, + true, ), throwsToolExit()); }, overrides: <Type, Generator>{ Platform: () => fakePlatform,
diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart index e5202ba..dac00f9 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart
@@ -392,6 +392,40 @@ ProcessManager: () => processManager, })); + test('Dart2JSTarget calls dart2js with expected args in release mode with native null assertions', () => testbed.run(() async { + environment.defines[kBuildMode] = 'release'; + environment.defines[kNativeNullAssertions] = 'true'; + processManager.addCommand(FakeCommand( + command: <String>[ + ...kDart2jsLinuxArgs, + '--native-null-assertions', + '-Ddart.vm.product=true', + '--no-source-maps', + '-o', + environment.buildDir.childFile('app.dill').absolute.path, + '--packages=.packages', + '--cfe-only', + environment.buildDir.childFile('main.dart').absolute.path, + ] + )); + processManager.addCommand(FakeCommand( + command: <String>[ + ...kDart2jsLinuxArgs, + '--native-null-assertions', + '-Ddart.vm.product=true', + '--no-source-maps', + '-O4', + '-o', + environment.buildDir.childFile('main.dart.js').absolute.path, + environment.buildDir.childFile('app.dill').absolute.path, + ] + )); + + await const Dart2JSTarget().build(environment); + }, overrides: <Type, Generator>{ + ProcessManager: () => processManager, + })); + test('Dart2JSTarget calls dart2js with expected args in release with dart2js optimization override', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; environment.defines[kDart2jsOptimization] = 'O3';
diff --git a/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart b/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart index 5bb0bd6..0cb5888 100644 --- a/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart +++ b/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart
@@ -24,6 +24,7 @@ final String result = generateMainModule( entrypoint: 'foo/bar/main.js', nullAssertions: false, + nativeNullAssertions: false, ); // bootstrap main module has correct defined module. expect(result, contains('define("main_module.bootstrap", ["foo/bar/main.js", "dart_sdk"], ' @@ -34,6 +35,7 @@ final String result = generateMainModule( entrypoint: 'foo/bar/main.js', nullAssertions: false, + nativeNullAssertions: false, bootstrapModule: 'foo_module.bootstrap', ); // bootstrap main module has correct defined module. @@ -45,11 +47,22 @@ final String result = generateMainModule( entrypoint: 'foo/bar/main.js', nullAssertions: true, + nativeNullAssertions: true, ); - expect(result, contains(''' - if (true) { - dart_sdk.dart.nonNullAsserts(true);''')); + expect(result, contains('''dart_sdk.dart.nonNullAsserts(true);''')); + expect(result, contains('''dart_sdk.dart.nativeNonNullAsserts(true);''')); + }); + + test('generateMainModule can disable null safety switches', () { + final String result = generateMainModule( + entrypoint: 'foo/bar/main.js', + nullAssertions: false, + nativeNullAssertions: false, + ); + + expect(result, contains('''dart_sdk.dart.nonNullAsserts(false);''')); + expect(result, contains('''dart_sdk.dart.nativeNonNullAsserts(false);''')); }); test('generateTestBootstrapFileContents embeds urls correctly', () {
diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart index 77228e4..2d580b2 100644 --- a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart +++ b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart
@@ -617,6 +617,7 @@ useSseForDebugProxy: true, useSseForDebugBackend: true, nullAssertions: true, + nativeNullAssertions: true, buildInfo: const BuildInfo( BuildMode.debug, '', @@ -732,6 +733,7 @@ useSseForDebugProxy: true, useSseForDebugBackend: true, nullAssertions: true, + nativeNullAssertions: true, buildInfo: const BuildInfo( BuildMode.debug, '', @@ -850,6 +852,7 @@ expressionCompiler: null, chromiumLauncher: null, nullAssertions: true, + nativeNullAssertions: true, nullSafetyMode: NullSafetyMode.sound, ); webDevFS.requireJS.createSync(recursive: true); @@ -886,6 +889,7 @@ useSseForDebugProxy: true, useSseForDebugBackend: true, nullAssertions: true, + nativeNullAssertions: true, buildInfo: const BuildInfo( BuildMode.debug, '', @@ -936,6 +940,7 @@ useSseForDebugProxy: true, useSseForDebugBackend: true, nullAssertions: true, + nativeNullAssertions: true, buildInfo: const BuildInfo( BuildMode.debug, '',