[CFE] Fail silently on invalid constructs during constant evaluation.

This acts as a stable backstop for situations where Fasta fails to
rewrite various erroneous constructs into invalid expressions.

Change-Id: Ibaac3d62b9cfdc597c0f85aadf9aec2920689222
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97511
Reviewed-by: Kevin Millikin <kmillikin@google.com>
Commit-Queue: Aske Simon Christensen <askesc@google.com>
diff --git a/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart b/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
index f255d8f..3f0e063 100644
--- a/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
+++ b/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
@@ -1217,6 +1217,38 @@
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Template<
     Message Function(
+        String string,
+        Constant
+            _constant)> templateConstEvalInvalidPropertyGet = const Template<
+        Message Function(String string, Constant _constant)>(
+    messageTemplate:
+        r"""The property '#string' can't be accessed on '#constant' within a const context.""",
+    withArguments: _withArgumentsConstEvalInvalidPropertyGet);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(String string, Constant _constant)>
+    codeConstEvalInvalidPropertyGet =
+    const Code<Message Function(String string, Constant _constant)>(
+        "ConstEvalInvalidPropertyGet", templateConstEvalInvalidPropertyGet,
+        analyzerCodes: <String>["CONST_EVAL_THROWS_EXCEPTION"]);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsConstEvalInvalidPropertyGet(
+    String string, Constant _constant) {
+  if (string.isEmpty) throw 'No string provided';
+  TypeLabeler labeler = new TypeLabeler();
+  List<Object> constantParts = labeler.labelConstant(_constant);
+  String constant = constantParts.join();
+  return new Message(codeConstEvalInvalidPropertyGet,
+      message:
+          """The property '${string}' can't be accessed on '${constant}' within a const context.""" +
+              labeler.originMessages,
+      arguments: {'string': string, 'constant': _constant});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<
+    Message Function(
         String
             name)> templateConstEvalInvalidStaticInvocation = const Template<
         Message Function(String name)>(
diff --git a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
index 79aa7f5..e8527f0 100644
--- a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
@@ -48,6 +48,7 @@
         templateConstEvalInvalidType,
         templateConstEvalInvalidBinaryOperandType,
         templateConstEvalInvalidMethodInvocation,
+        templateConstEvalInvalidPropertyGet,
         templateConstEvalInvalidStaticInvocation,
         templateConstEvalInvalidStringInterpolationOperand,
         templateConstEvalInvalidSymbolName,
@@ -478,17 +479,27 @@
       errorReporter.report(locatedMessage, contextMessages);
       return new UnevaluatedConstant(new InvalidExpression(e.message.message));
     } on _AbortDueToInvalidExpression catch (e) {
-      errorReporter.reportInvalidExpression(e.node);
-      return new UnevaluatedConstant(e.node);
+      // TODO(askesc): Copy position from erroneous node.
+      // Currently not possible, as it might be in a different file.
+      // Can be done if we add an explicit URI to InvalidExpression.
+      InvalidExpression invalid = new InvalidExpression(e.message);
+      if (invalid.fileOffset == TreeNode.noOffset) {
+        invalid.fileOffset = node.fileOffset;
+      }
+      errorReporter.reportInvalidExpression(invalid);
+      return new UnevaluatedConstant(invalid);
     }
   }
 
+  /// Report an error that has been detected during constant evaluation.
   Null report(TreeNode node, Message message, {List<LocatedMessage> context}) {
     throw new _AbortDueToError(node, message, context: context);
   }
 
-  Null reportInvalidExpression(InvalidExpression node) {
-    throw new _AbortDueToInvalidExpression(node);
+  /// Report a construct that should not occur inside a potentially constant
+  /// expression. It is assumed that an error has already been reported.
+  Null reportInvalid(TreeNode node, String message) {
+    throw new _AbortDueToInvalidExpression(node, message);
   }
 
   /// Produce an unevaluated constant node for an expression.
@@ -575,7 +586,8 @@
   defaultTreeNode(Node node) {
     // Only a subset of the expression language is valid for constant
     // evaluation.
-    throw 'Constant evaluation has no support for ${node.runtimeType} yet!';
+    return reportInvalid(
+        node, 'Constant evaluation has no support for ${node.runtimeType}!');
   }
 
   visitNullLiteral(NullLiteral node) => nullConstant;
@@ -707,15 +719,18 @@
     final Constructor constructor = node.target;
     final Class klass = constructor.enclosingClass;
     if (!constructor.isConst) {
-      throw 'The front-end should ensure we do not encounter a '
-          'constructor invocation of a non-const constructor.';
+      return reportInvalid(node, 'Non-const constructor invocation.');
     }
     if (constructor.function.body != null &&
         constructor.function.body is! EmptyStatement) {
-      throw 'Constructor "$node" has non-trivial body "${constructor.function.body.runtimeType}".';
+      return reportInvalid(
+          node,
+          'Constructor "$node" has non-trivial body '
+          '"${constructor.function.body.runtimeType}".');
     }
     if (klass.isAbstract) {
-      throw 'Constructor "$node" belongs to abstract class "${klass}".';
+      return reportInvalid(
+          node, 'Constructor "$node" belongs to abstract class "${klass}".');
     }
 
     final positionals = evaluatePositionalArguments(node.arguments);
@@ -993,8 +1008,10 @@
               }
             }
           } else {
-            throw new Exception(
-                'No support for handling initializer of type "${init.runtimeType}".');
+            return reportInvalid(
+                constructor,
+                'No support for handling initializer of type '
+                '"${init.runtimeType}".');
           }
         }
       });
@@ -1002,7 +1019,7 @@
   }
 
   visitInvalidExpression(InvalidExpression node) {
-    return reportInvalidExpression(node);
+    return reportInvalid(node, node.message);
   }
 
   visitMethodInvocation(MethodInvocation node) {
@@ -1260,12 +1277,16 @@
   visitPropertyGet(PropertyGet node) {
     if (node.receiver is ThisExpression) {
       // Access "this" during instance creation.
+      if (instanceBuilder == null) {
+        return reportInvalid(node, 'Instance field access outside constructor');
+      }
       for (final Field field in instanceBuilder.fields.keys) {
         if (field.name == node.name) {
           return instanceBuilder.fields[field];
         }
       }
-      throw 'Could not evaluate field get ${node.name} on incomplete instance';
+      return reportInvalid(node,
+          'Could not evaluate field get ${node.name} on incomplete instance');
     }
 
     final Constant receiver = _evaluateSubexpression(node.receiver);
@@ -1283,7 +1304,10 @@
     } else if (receiver is NullConstant) {
       return report(node, messageConstEvalNullValue);
     }
-    throw 'Could not evaluate property get on $receiver.';
+    return report(
+        node,
+        templateConstEvalInvalidPropertyGet.withArguments(
+            node.name.name, receiver));
   }
 
   visitLet(Let node) {
@@ -1310,8 +1334,7 @@
     if (variable.isConst) {
       return _evaluateSubexpression(variable.initializer);
     }
-    throw new Exception('The front-end should ensure we do not encounter a '
-        'variable get of a non-const variable.');
+    return reportInvalid(node, 'Variable get of a non-const variable.');
   }
 
   visitStaticGet(StaticGet node) {
@@ -1340,8 +1363,8 @@
             templateConstEvalInvalidStaticInvocation
                 .withArguments(target.name.name));
       } else {
-        throw new Exception(
-            'No support for ${target.runtimeType} in a static-get.');
+        reportInvalid(
+            node, 'No support for ${target.runtimeType} in a static-get.');
       }
     });
   }
@@ -1530,14 +1553,15 @@
         return canonicalize(
             new PartialInstantiationConstant(constant, typeArguments));
       }
-      throw new Exception(
+      return reportInvalid(
+          node,
           'The number of type arguments supplied in the partial instantiation '
           'does not match the number of type arguments of the $constant.');
     }
     // The inner expression in an instantiation can never be null, since
     // instantiations are only inferred on direct references to declarations.
-    throw new Exception(
-        'Only tear-off constants can be partially instantiated.');
+    return reportInvalid(
+        node, 'Only tear-off constants can be partially instantiated.');
   }
 
   @override
@@ -1701,7 +1725,7 @@
         return a > b ? trueConstant : falseConstant;
     }
 
-    throw new Exception("Unexpected binary numeric operation '$op'.");
+    return reportInvalid(node, "Unexpected binary numeric operation '$op'.");
   }
 
   Library libraryOf(TreeNode node) {
@@ -1793,9 +1817,10 @@
 }
 
 class _AbortDueToInvalidExpression {
-  final InvalidExpression node;
+  final TreeNode node;
+  final String message;
 
-  _AbortDueToInvalidExpression(this.node);
+  _AbortDueToInvalidExpression(this.node, this.message);
 }
 
 abstract class ErrorReporter {
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index 111ca1a..ee438b8 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -72,6 +72,7 @@
 ConstEvalInvalidBinaryOperandType/analyzerCode: Fail # CONST_EVAL_TYPE_NUM / CONST_EVAL_TYPE_BOOL
 ConstEvalInvalidBinaryOperandType/example: Fail
 ConstEvalInvalidMethodInvocation/example: Fail
+ConstEvalInvalidPropertyGet/example: Fail
 ConstEvalInvalidStaticInvocation/example: Fail
 ConstEvalInvalidStringInterpolationOperand/example: Fail
 ConstEvalInvalidSymbolName/example: Fail
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index 22a299c..d5825f7 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -110,6 +110,10 @@
   template: "The method '#string' can't be invoked on '#constant' within a const context."
   analyzerCode: UNDEFINED_OPERATOR
 
+ConstEvalInvalidPropertyGet:
+  template: "The property '#string' can't be accessed on '#constant' within a const context."
+  analyzerCode: CONST_EVAL_THROWS_EXCEPTION
+
 ConstEvalInvalidStringInterpolationOperand:
   template: "The '#constant' can't be used as part of a string interpolation within a const context, only values of type 'null', 'bool', 'int', 'double', or 'String' can be used."
   analyzerCode: CONST_EVAL_TYPE_BOOL_NUM_STRING