Unverified Commit 9e5c8771 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] wire up native-null-assertions for flutter web (#71618)

parent 088b7c36
...@@ -39,6 +39,9 @@ const String kServiceWorkerStrategy = 'ServiceWorkerStrategy'; ...@@ -39,6 +39,9 @@ const String kServiceWorkerStrategy = 'ServiceWorkerStrategy';
/// Whether the dart2js build should output source maps. /// Whether the dart2js build should output source maps.
const String kSourceMapsEnabled = 'SourceMaps'; const String kSourceMapsEnabled = 'SourceMaps';
/// Whether the dart2js native null assertions are enabled.
const String kNativeNullAssertions = 'NativeNullAssertions';
/// The caching strategy for the generated service worker. /// The caching strategy for the generated service worker.
enum ServiceWorkerStrategy { enum ServiceWorkerStrategy {
/// Download the app shell eagerly and all other assets lazily. /// Download the app shell eagerly and all other assets lazily.
...@@ -190,6 +193,7 @@ class Dart2JSTarget extends Target { ...@@ -190,6 +193,7 @@ class Dart2JSTarget extends Target {
Future<void> build(Environment environment) async { Future<void> build(Environment environment) async {
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
final bool sourceMapsEnabled = environment.defines[kSourceMapsEnabled] == 'true'; final bool sourceMapsEnabled = environment.defines[kSourceMapsEnabled] == 'true';
final bool nativeNullAssertions = environment.defines[kNativeNullAssertions] == 'true';
final List<String> sharedCommandOptions = <String>[ final List<String> sharedCommandOptions = <String>[
globals.artifacts.getArtifactPath(Artifact.engineDartBinary), globals.artifacts.getArtifactPath(Artifact.engineDartBinary),
...@@ -197,6 +201,8 @@ class Dart2JSTarget extends Target { ...@@ -197,6 +201,8 @@ class Dart2JSTarget extends Target {
globals.artifacts.getArtifactPath(Artifact.dart2jsSnapshot), globals.artifacts.getArtifactPath(Artifact.dart2jsSnapshot),
'--libraries-spec=${globals.fs.path.join(globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), 'libraries.json')}', '--libraries-spec=${globals.fs.path.join(globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), 'libraries.json')}',
...?decodeDartDefines(environment.defines, kExtraFrontEndOptions), ...?decodeDartDefines(environment.defines, kExtraFrontEndOptions),
if (nativeNullAssertions)
'--native-null-assertions',
if (buildMode == BuildMode.profile) if (buildMode == BuildMode.profile)
'-Ddart.vm.profile=true' '-Ddart.vm.profile=true'
else else
......
...@@ -26,6 +26,7 @@ class BuildWebCommand extends BuildSubCommand { ...@@ -26,6 +26,7 @@ class BuildWebCommand extends BuildSubCommand {
usesWebRendererOption(); usesWebRendererOption();
addEnableExperimentation(hide: !verboseHelp); addEnableExperimentation(hide: !verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp); addNullSafetyModeOptions(hide: !verboseHelp);
addNativeNullAssertions(hide: false);
argParser.addFlag('csp', argParser.addFlag('csp',
defaultsTo: false, defaultsTo: false,
negatable: false, negatable: false,
...@@ -90,6 +91,7 @@ class BuildWebCommand extends BuildSubCommand { ...@@ -90,6 +91,7 @@ class BuildWebCommand extends BuildSubCommand {
boolArg('csp'), boolArg('csp'),
stringArg('pwa-strategy'), stringArg('pwa-strategy'),
boolArg('source-maps'), boolArg('source-maps'),
boolArg('native-null-assertions'),
); );
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }
......
...@@ -30,6 +30,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment ...@@ -30,6 +30,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
usesDartDefineOption(); usesDartDefineOption();
usesFlavorOption(); usesFlavorOption();
usesWebRendererOption(); usesWebRendererOption();
addNativeNullAssertions(hide: !verboseHelp);
argParser argParser
..addFlag('trace-startup', ..addFlag('trace-startup',
negatable: false, negatable: false,
...@@ -205,6 +206,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment ...@@ -205,6 +206,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
&& boolArg('fast-start') && boolArg('fast-start')
&& !runningWithPrebuiltApplication, && !runningWithPrebuiltApplication,
nullAssertions: boolArg('null-assertions'), nullAssertions: boolArg('null-assertions'),
nativeNullAssertions: boolArg('native-null-assertions'),
); );
} }
} }
......
...@@ -863,6 +863,7 @@ class DebuggingOptions { ...@@ -863,6 +863,7 @@ class DebuggingOptions {
this.vmserviceOutFile, this.vmserviceOutFile,
this.fastStart = false, this.fastStart = false,
this.nullAssertions = false, this.nullAssertions = false,
this.nativeNullAssertions = false,
}) : debuggingEnabled = true; }) : debuggingEnabled = true;
DebuggingOptions.disabled(this.buildInfo, { DebuggingOptions.disabled(this.buildInfo, {
...@@ -897,7 +898,8 @@ class DebuggingOptions { ...@@ -897,7 +898,8 @@ class DebuggingOptions {
vmserviceOutFile = null, vmserviceOutFile = null,
fastStart = false, fastStart = false,
webEnableExpressionEvaluation = false, webEnableExpressionEvaluation = false,
nullAssertions = false; nullAssertions = false,
nativeNullAssertions = false;
final bool debuggingEnabled; final bool debuggingEnabled;
...@@ -947,6 +949,12 @@ class DebuggingOptions { ...@@ -947,6 +949,12 @@ class DebuggingOptions {
final bool nullAssertions; 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; bool get hasObservatoryPort => hostVmServicePort != null;
} }
......
...@@ -605,6 +605,7 @@ class WebDevFS implements DevFS { ...@@ -605,6 +605,7 @@ class WebDevFS implements DevFS {
@required this.expressionCompiler, @required this.expressionCompiler,
@required this.chromiumLauncher, @required this.chromiumLauncher,
@required this.nullAssertions, @required this.nullAssertions,
@required this.nativeNullAssertions,
@required this.nullSafetyMode, @required this.nullSafetyMode,
this.testMode = false, this.testMode = false,
}) : _port = port; }) : _port = port;
...@@ -621,6 +622,7 @@ class WebDevFS implements DevFS { ...@@ -621,6 +622,7 @@ class WebDevFS implements DevFS {
final ExpressionCompiler expressionCompiler; final ExpressionCompiler expressionCompiler;
final ChromiumLauncher chromiumLauncher; final ChromiumLauncher chromiumLauncher;
final bool nullAssertions; final bool nullAssertions;
final bool nativeNullAssertions;
final int _port; final int _port;
final NullSafetyMode nullSafetyMode; final NullSafetyMode nullSafetyMode;
...@@ -779,6 +781,7 @@ class WebDevFS implements DevFS { ...@@ -779,6 +781,7 @@ class WebDevFS implements DevFS {
generateMainModule( generateMainModule(
entrypoint: entrypoint, entrypoint: entrypoint,
nullAssertions: nullAssertions, nullAssertions: nullAssertions,
nativeNullAssertions: nativeNullAssertions,
), ),
); );
// TODO(jonahwilliams): refactor the asset code in this and the regular devfs to // TODO(jonahwilliams): refactor the asset code in this and the regular devfs to
......
...@@ -501,6 +501,7 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -501,6 +501,7 @@ class _ResidentWebRunner extends ResidentWebRunner {
chromiumLauncher: _chromiumLauncher, chromiumLauncher: _chromiumLauncher,
nullAssertions: debuggingOptions.nullAssertions, nullAssertions: debuggingOptions.nullAssertions,
nullSafetyMode: debuggingOptions.buildInfo.nullSafetyMode, nullSafetyMode: debuggingOptions.buildInfo.nullSafetyMode,
nativeNullAssertions: debuggingOptions.nativeNullAssertions,
); );
final Uri url = await device.devFS.create(); final Uri url = await device.devFS.create();
if (debuggingOptions.buildInfo.isDebug) { if (debuggingOptions.buildInfo.isDebug) {
...@@ -520,6 +521,7 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -520,6 +521,7 @@ class _ResidentWebRunner extends ResidentWebRunner {
false, false,
kNoneWorker, kNoneWorker,
true, true,
debuggingOptions.nativeNullAssertions,
); );
} }
await device.device.startApp( await device.device.startApp(
...@@ -587,6 +589,7 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -587,6 +589,7 @@ class _ResidentWebRunner extends ResidentWebRunner {
false, false,
kNoneWorker, kNoneWorker,
true, true,
debuggingOptions.nativeNullAssertions,
); );
} on ToolExit { } on ToolExit {
return OperationResult(1, 'Failed to recompile application.'); return OperationResult(1, 'Failed to recompile application.');
......
...@@ -674,6 +674,19 @@ abstract class FlutterCommand extends Command<void> { ...@@ -674,6 +674,19 @@ abstract class FlutterCommand extends Command<void> {
); );
} }
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. /// Adds build options common to all of the desktop build commands.
void addCommonDesktopBuildOptions({ bool verboseHelp = false }) { void addCommonDesktopBuildOptions({ bool verboseHelp = false }) {
addBuildModeFlags(verboseHelp: verboseHelp); addBuildModeFlags(verboseHelp: verboseHelp);
......
...@@ -224,6 +224,7 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -224,6 +224,7 @@ class FlutterWebPlatform extends PlatformPlugin {
final String generatedFile = _fileSystem.path.split(leadingPath).join('_') + '.dart.test.dart.js'; final String generatedFile = _fileSystem.path.split(leadingPath).join('_') + '.dart.test.dart.js';
return shelf.Response.ok(generateMainModule( return shelf.Response.ok(generateMainModule(
nullAssertions: nullAssertions, nullAssertions: nullAssertions,
nativeNullAssertions: true,
bootstrapModule: _fileSystem.path.basename(leadingPath) + '.dart.bootstrap', bootstrapModule: _fileSystem.path.basename(leadingPath) + '.dart.bootstrap',
entrypoint: '/' + generatedFile entrypoint: '/' + generatedFile
), headers: <String, String>{ ), headers: <String, String>{
......
...@@ -54,6 +54,7 @@ document.head.appendChild(requireEl); ...@@ -54,6 +54,7 @@ document.head.appendChild(requireEl);
String generateMainModule({ String generateMainModule({
@required String entrypoint, @required String entrypoint,
@required bool nullAssertions, @required bool nullAssertions,
@required bool nativeNullAssertions,
String bootstrapModule = 'main_module.bootstrap', String bootstrapModule = 'main_module.bootstrap',
}) { }) {
// TODO(jonahwilliams): fix typo in dwds and update. // TODO(jonahwilliams): fix typo in dwds and update.
...@@ -63,9 +64,8 @@ String generateMainModule({ ...@@ -63,9 +64,8 @@ String generateMainModule({
define("$bootstrapModule", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) { define("$bootstrapModule", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) {
dart_sdk.dart.setStartAsyncSynchronously(true); dart_sdk.dart.setStartAsyncSynchronously(true);
dart_sdk._debugger.registerDevtoolsFormatter(); dart_sdk._debugger.registerDevtoolsFormatter();
if ($nullAssertions) { dart_sdk.dart.nonNullAsserts($nullAssertions);
dart_sdk.dart.nonNullAsserts(true); dart_sdk.dart.nativeNonNullAsserts($nativeNullAssertions);
}
// See the generateMainModule doc comment. // See the generateMainModule doc comment.
var child = {}; var child = {};
......
...@@ -24,6 +24,7 @@ Future<void> buildWeb( ...@@ -24,6 +24,7 @@ Future<void> buildWeb(
bool csp, bool csp,
String serviceWorkerStrategy, String serviceWorkerStrategy,
bool sourceMaps, bool sourceMaps,
bool nativeNullAssertions,
) async { ) async {
if (!flutterProject.web.existsSync()) { if (!flutterProject.web.existsSync()) {
throwToolExit('Missing index.html.'); throwToolExit('Missing index.html.');
...@@ -51,6 +52,7 @@ Future<void> buildWeb( ...@@ -51,6 +52,7 @@ Future<void> buildWeb(
kCspMode: csp.toString(), kCspMode: csp.toString(),
kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(), kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(),
kSourceMapsEnabled: sourceMaps.toString(), kSourceMapsEnabled: sourceMaps.toString(),
kNativeNullAssertions: nativeNullAssertions.toString(),
if (serviceWorkerStrategy != null) if (serviceWorkerStrategy != null)
kServiceWorkerStrategy: serviceWorkerStrategy, kServiceWorkerStrategy: serviceWorkerStrategy,
if (buildInfo.extraFrontEndOptions?.isNotEmpty ?? false) if (buildInfo.extraFrontEndOptions?.isNotEmpty ?? false)
......
...@@ -56,6 +56,7 @@ void main() { ...@@ -56,6 +56,7 @@ void main() {
false, false,
null, null,
true, true,
true,
), throwsToolExit()); ), throwsToolExit());
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => fakePlatform, Platform: () => fakePlatform,
......
...@@ -392,6 +392,40 @@ void main() { ...@@ -392,6 +392,40 @@ void main() {
ProcessManager: () => processManager, 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 { test('Dart2JSTarget calls dart2js with expected args in release with dart2js optimization override', () => testbed.run(() async {
environment.defines[kBuildMode] = 'release'; environment.defines[kBuildMode] = 'release';
environment.defines[kDart2jsOptimization] = 'O3'; environment.defines[kDart2jsOptimization] = 'O3';
......
...@@ -24,6 +24,7 @@ void main() { ...@@ -24,6 +24,7 @@ void main() {
final String result = generateMainModule( final String result = generateMainModule(
entrypoint: 'foo/bar/main.js', entrypoint: 'foo/bar/main.js',
nullAssertions: false, nullAssertions: false,
nativeNullAssertions: false,
); );
// bootstrap main module has correct defined module. // bootstrap main module has correct defined module.
expect(result, contains('define("main_module.bootstrap", ["foo/bar/main.js", "dart_sdk"], ' expect(result, contains('define("main_module.bootstrap", ["foo/bar/main.js", "dart_sdk"], '
...@@ -34,6 +35,7 @@ void main() { ...@@ -34,6 +35,7 @@ void main() {
final String result = generateMainModule( final String result = generateMainModule(
entrypoint: 'foo/bar/main.js', entrypoint: 'foo/bar/main.js',
nullAssertions: false, nullAssertions: false,
nativeNullAssertions: false,
bootstrapModule: 'foo_module.bootstrap', bootstrapModule: 'foo_module.bootstrap',
); );
// bootstrap main module has correct defined module. // bootstrap main module has correct defined module.
...@@ -45,11 +47,22 @@ void main() { ...@@ -45,11 +47,22 @@ void main() {
final String result = generateMainModule( final String result = generateMainModule(
entrypoint: 'foo/bar/main.js', entrypoint: 'foo/bar/main.js',
nullAssertions: true, nullAssertions: true,
nativeNullAssertions: true,
); );
expect(result, contains(''' expect(result, contains('''dart_sdk.dart.nonNullAsserts(true);'''));
if (true) { expect(result, contains('''dart_sdk.dart.nativeNonNullAsserts(true);'''));
dart_sdk.dart.nonNullAsserts(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', () { test('generateTestBootstrapFileContents embeds urls correctly', () {
......
...@@ -617,6 +617,7 @@ void main() { ...@@ -617,6 +617,7 @@ void main() {
useSseForDebugProxy: true, useSseForDebugProxy: true,
useSseForDebugBackend: true, useSseForDebugBackend: true,
nullAssertions: true, nullAssertions: true,
nativeNullAssertions: true,
buildInfo: const BuildInfo( buildInfo: const BuildInfo(
BuildMode.debug, BuildMode.debug,
'', '',
...@@ -732,6 +733,7 @@ void main() { ...@@ -732,6 +733,7 @@ void main() {
useSseForDebugProxy: true, useSseForDebugProxy: true,
useSseForDebugBackend: true, useSseForDebugBackend: true,
nullAssertions: true, nullAssertions: true,
nativeNullAssertions: true,
buildInfo: const BuildInfo( buildInfo: const BuildInfo(
BuildMode.debug, BuildMode.debug,
'', '',
...@@ -850,6 +852,7 @@ void main() { ...@@ -850,6 +852,7 @@ void main() {
expressionCompiler: null, expressionCompiler: null,
chromiumLauncher: null, chromiumLauncher: null,
nullAssertions: true, nullAssertions: true,
nativeNullAssertions: true,
nullSafetyMode: NullSafetyMode.sound, nullSafetyMode: NullSafetyMode.sound,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
...@@ -886,6 +889,7 @@ void main() { ...@@ -886,6 +889,7 @@ void main() {
useSseForDebugProxy: true, useSseForDebugProxy: true,
useSseForDebugBackend: true, useSseForDebugBackend: true,
nullAssertions: true, nullAssertions: true,
nativeNullAssertions: true,
buildInfo: const BuildInfo( buildInfo: const BuildInfo(
BuildMode.debug, BuildMode.debug,
'', '',
...@@ -936,6 +940,7 @@ void main() { ...@@ -936,6 +940,7 @@ void main() {
useSseForDebugProxy: true, useSseForDebugProxy: true,
useSseForDebugBackend: true, useSseForDebugBackend: true,
nullAssertions: true, nullAssertions: true,
nativeNullAssertions: true,
buildInfo: const BuildInfo( buildInfo: const BuildInfo(
BuildMode.debug, BuildMode.debug,
'', '',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment