[analyzer] Fix dead code reporting for null-aware accesses.

A null-aware access can result in dead code if the target has static
type `Null`. The analyzer wasn't properly accounting for this,
resulting in some confusing ranges reported for the DEAD_CODE warning.

This change causes the following expressions to report dead code for
the code ranges indiced by `^`:

    Null myNullVar = null;
    myNullVar?[index];
    //         ^^^^^^ DEAD_CODE
    myNullVar?[index] = value;
    //         ^^^^^^^^^^^^^^ DEAD_CODE
    myNullVar?.method();
    //         ^^^^^^^^ DEAD_CODE
    myNullVar?.property;
    //         ^^^^^^^^ DEAD_CODE
    myNullVar?.property = value;
    //         ^^^^^^^^^^^^^^^^ DEAD_CODE

Note that the bug was confined solely to the logic that reports the
DEAD_CODE warning; there is no change to the reachability inferred by
flow analysis (and hence, this is a non-breaking change).

Fixes https://github.com/dart-lang/sdk/issues/60364.

Bug: https://github.com/dart-lang/sdk/issues/60364
Change-Id: I068826282fba6b9057e9c27d1d9310c65714e203
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/416723
Auto-Submit: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/unreachable_via_never.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/unreachable_via_never.dart
index 7800df9..c2c740e 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/unreachable_via_never.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/unreachable_via_never.dart
@@ -50,7 +50,7 @@
 }
 
 void nullAwareAccess(Null Function() f, Object? Function() g) {
-  f()?.extensionMethod(/*unreachable*/ 1);
+  f()?./*analyzer.unreachable*/extensionMethod(/*unreachable*/ 1);
   g()?.extensionMethod(2);
 }
 
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index be27c65..c4876dd 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -1239,6 +1239,7 @@
       do {
         _unfinishedNullShorts.removeLast();
         flowAnalysis.flow!.nullAwareAccess_end();
+        nullSafetyDeadCodeVerifier.flowEnd(node);
       } while (identical(_unfinishedNullShorts.last, node));
       if (node is! CascadeExpression) {
         // Make the static type of `node` (or whatever it was rewritten to)
@@ -1404,6 +1405,7 @@
 
       if (node.isNullAware) {
         _startNullAwareAccess(node, node.target);
+        nullSafetyDeadCodeVerifier.visitNode(node.index);
       }
 
       var result = _propertyElementResolver.resolveIndexExpression(
@@ -1457,6 +1459,7 @@
       }
       if (node.isNullAware) {
         _startNullAwareAccess(node, node.target);
+        nullSafetyDeadCodeVerifier.visitNode(node.propertyName);
       }
 
       inferenceLogWriter?.exitLValue(node);
@@ -2838,6 +2841,7 @@
 
     if (node.isNullAware) {
       _startNullAwareAccess(node, node.target);
+      nullSafetyDeadCodeVerifier.visitNode(node.index);
     }
 
     var result = _propertyElementResolver.resolveIndexExpression(
@@ -3055,6 +3059,7 @@
 
     if (node.isNullAware) {
       _startNullAwareAccess(node, target);
+      nullSafetyDeadCodeVerifier.visitNode(node.methodName);
     }
 
     node.typeArguments?.accept(this);
@@ -3945,6 +3950,7 @@
       PropertyAccessImpl node, TypeImpl contextType) {
     if (node.isNullAware) {
       _startNullAwareAccess(node, node.target);
+      nullSafetyDeadCodeVerifier.visitNode(node.propertyName);
     }
 
     var result = _propertyElementResolver.resolvePropertyAccess(
@@ -4009,6 +4015,7 @@
 
     if (function is PropertyAccessImpl && function.isNullAware) {
       _startNullAwareAccess(function, function.target);
+      nullSafetyDeadCodeVerifier.visitNode(node.argumentList);
     }
 
     _functionExpressionInvocationResolver.resolve(node, whyNotPromotedArguments,
diff --git a/pkg/analyzer/test/src/diagnostics/dead_code_test.dart b/pkg/analyzer/test/src/diagnostics/dead_code_test.dart
index 872cf69..7e2f4a9 100644
--- a/pkg/analyzer/test/src/diagnostics/dead_code_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/dead_code_test.dart
@@ -202,6 +202,66 @@
     ]);
   }
 
+  test_nullAwareIndexedRead() async {
+    await assertErrorsInCode(r'''
+void f(Null n, int i) {
+  n?[i];
+  print('reached');
+}
+''', [
+      // Dead range: `i]`
+      error(WarningCode.DEAD_CODE, 29, 2)
+    ]);
+  }
+
+  test_nullAwareIndexedWrite() async {
+    await assertErrorsInCode(r'''
+void f(Null n, int i, int j) {
+  n?[i] = j;
+  print('reached');
+}
+''', [
+      // Dead range: `i] = j`
+      error(WarningCode.DEAD_CODE, 36, 6)
+    ]);
+  }
+
+  test_nullAwareMethodInvocation() async {
+    await assertErrorsInCode(r'''
+void f(Null n, int i) {
+  n?.foo(i);
+  print('reached');
+}
+''', [
+      // Dead range: `foo(i)`
+      error(WarningCode.DEAD_CODE, 29, 6)
+    ]);
+  }
+
+  test_nullAwarePropertyRead() async {
+    await assertErrorsInCode(r'''
+void f(Null n) {
+  n?.p;
+  print('reached');
+}
+''', [
+      // Dead range: `p`
+      error(WarningCode.DEAD_CODE, 22, 1)
+    ]);
+  }
+
+  test_nullAwarePropertyWrite() async {
+    await assertErrorsInCode(r'''
+void f(Null n, int i) {
+  n?.p = i;
+  print('reached');
+}
+''', [
+      // Dead range: `p = i`
+      error(WarningCode.DEAD_CODE, 29, 5)
+    ]);
+  }
+
   test_objectPattern_neverTypedGetter() async {
     await assertErrorsInCode(r'''
 class A {