[ddc] Add nullability assertions for native values

Bug: https://github.com/dart-lang/sdk/issues/42535

Adds a runtime flag to ddc to enable runtime checks on native APIs.
In the case where an API is typed non-nullable but returns a null
value, throws a null assertion error.

Change-Id: I4d5d7529ba28d9308687dad5d51f1b9c71274455
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/162461
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Commit-Queue: Srujan Gaddam <srujzs@google.com>
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index d6ee556..f4d1611 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -4200,8 +4200,14 @@
 
   @override
   js_ast.Expression visitPropertyGet(PropertyGet node) {
-    return _emitPropertyGet(
-        node.receiver, node.interfaceTarget, node.name.text);
+    var propertyGet =
+        _emitPropertyGet(node.receiver, node.interfaceTarget, node.name.text);
+    if (_isCheckableNative(node.interfaceTarget)) {
+      // If target is a native getter with a non-nullable type, add a null check
+      // for soundness.
+      return runtimeCall('checkNativeNonNull(#)', [propertyGet]);
+    }
+    return propertyGet;
   }
 
   @override
@@ -4264,6 +4270,16 @@
     }
   }
 
+  /// Return whether [member] returns a native object whose type needs to be
+  /// checked.
+  bool _isCheckableNative(Member member) =>
+      member != null &&
+      member.isExternal &&
+      _extensionTypes.isNativeClass(member.enclosingClass) &&
+      member is Procedure &&
+      member.function != null &&
+      member.function.returnType.isPotentiallyNonNullable;
+
   // TODO(jmesserly): can we encapsulate REPL name lookups and remove this?
   // _emitMemberName would be a nice place to handle it, but we don't have
   // access to the target expression there (needed for `dart.replNameLookup`).
@@ -4334,8 +4350,14 @@
 
   @override
   js_ast.Expression visitMethodInvocation(MethodInvocation node) {
-    return _emitMethodCall(
+    var methodCall = _emitMethodCall(
         node.receiver, node.interfaceTarget, node.arguments, node);
+    if (_isCheckableNative(node.interfaceTarget)) {
+      // If target is a native method with a non-nullable type, add a null check
+      // for soundness.
+      return runtimeCall('checkNativeNonNull(#)', [methodCall]);
+    }
+    return methodCall;
   }
 
   @override
@@ -5066,11 +5088,30 @@
 
     var result = js.parseForeignJS(source).instantiate(jsArgs);
 
+    // Add a check to make sure any JS() values from a native type are typed
+    // properly.
+    if (_isWebLibrary(_currentLibrary.importUri)) {
+      var type = node.getStaticType(_staticTypeContext);
+      if (type.isPotentiallyNonNullable) {
+        result = runtimeCall('checkNativeNonNull(#)', [result]);
+      }
+    }
+
     assert(result is js_ast.Expression ||
         result is js_ast.Statement && node.parent is ExpressionStatement);
     return result;
   }
 
+  bool _isWebLibrary(Uri importUri) =>
+      importUri.scheme == 'dart' &&
+      (importUri.path == 'html' ||
+          importUri.path == 'svg' ||
+          importUri.path == 'indexed_db' ||
+          importUri.path == 'web_audio' ||
+          importUri.path == 'web_gl' ||
+          importUri.path == 'web_sql' ||
+          importUri.path == 'html_common');
+
   bool _isNull(Expression expr) =>
       expr is NullLiteral ||
       expr.getStaticType(_staticTypeContext) == _coreTypes.nullType;
diff --git a/pkg/dev_compiler/tool/ddb b/pkg/dev_compiler/tool/ddb
index b2cd5ce..126090a 100755
--- a/pkg/dev_compiler/tool/ddb
+++ b/pkg/dev_compiler/tool/ddb
@@ -54,6 +54,11 @@
             'parameters are not null.',
         defaultsTo: false,
         negatable: true)
+    ..addFlag('native-null-assertions',
+        help: 'Run with assertions on non-nullable values returned from native '
+            'APIs.',
+        defaultsTo: true,
+        negatable: true)
     ..addFlag('observe',
         help:
             'Run the compiler in the Dart VM with --observe. Implies --debug.',
@@ -109,6 +114,7 @@
   var run = mode == 'run' || mode == 'all';
   var verbose = options['verbose'] as bool;
   var nonNullAsserts = options['null-assertions'] as bool;
+  var nativeNonNullAsserts = options['null-assertions'] as bool;
 
   var soundNullSafety = options['sound-null-safety'] as bool;
   // Enable null safety either by passing the `non-nullable` experiment flag or
@@ -280,6 +286,7 @@
     if ($nnbd) {
       sdk.dart.weakNullSafetyWarnings(!$soundNullSafety);
       sdk.dart.nonNullAsserts($nonNullAsserts);
+      sdk.dart.nativeNonNullAsserts($nativeNonNullAsserts);
     }
     sdk._debugger.registerDevtoolsFormatter();
     app.$libname.main([]);
@@ -315,6 +322,7 @@
   if ($nnbd) {
     sdk.dart.weakNullSafetyWarnings(!$soundNullSafety);
     sdk.dart.nonNullAsserts($nonNullAsserts);
+    sdk.dart.nativeNonNullAsserts($nativeNonNullAsserts);
   }
   sdk._isolate_helper.startRootIsolate(main, []);
 } catch(e) {
@@ -351,6 +359,7 @@
   if ($nnbd) {
     dart.weakNullSafetyWarnings(!$soundNullSafety);
     dart.nonNullAsserts($nonNullAsserts);
+    dart.nativeNonNullAsserts($nativeNonNullAsserts);
   }
   _isolate_helper.startRootIsolate(() => {}, []);
   main([]);
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
index d931293..6fcd3d1 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
@@ -870,3 +870,10 @@
   }
   return ${defineProperty(to, name, desc)};
 })()''');
+
+checkNativeNonNull(dynamic variable) {
+  if (_nativeNonNullAsserts) {
+    return nullCheck(variable);
+  }
+  return variable;
+}
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart
index 8dd643c..acb1615 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart
@@ -44,6 +44,15 @@
   _nonNullAsserts = enable;
 }
 
+@notNull
+bool _nativeNonNullAsserts = false;
+
+/// Enables null assertions on native APIs to make sure value returned from the
+/// browser is sound. These apply to dart:html and similar web libraries.
+void nativeNonNullAsserts(bool enable) {
+  _nativeNonNullAsserts = enable;
+}
+
 final metadata = JS('', 'Symbol("metadata")');
 
 /// Types in dart are represented internally at runtime as follows.