reader/wgsl: Abort after raising too many errors

Change-Id: I641ee8c2e34e059a02742d06c24f96acecb39cd3
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/33720
Commit-Queue: David Neto <dneto@google.com>
Reviewed-by: David Neto <dneto@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
diff --git a/src/diagnostic/diagnostic.h b/src/diagnostic/diagnostic.h
index 21ce75b..6fea75f 100644
--- a/src/diagnostic/diagnostic.h
+++ b/src/diagnostic/diagnostic.h
@@ -83,13 +83,15 @@
   void add(Diagnostic&& diag) {
     entries_.emplace_back(std::move(diag));
     if (diag.severity >= Severity::Error) {
-      contains_errors_ = true;
+      error_count_++;
     }
   }
 
   /// @returns true iff the diagnostic list contains errors diagnostics (or of
   /// higher severity).
-  bool contains_errors() const { return contains_errors_; }
+  bool contains_errors() const { return error_count_ > 0; }
+  /// @returns the number of error diagnostics (or of higher severity).
+  size_t error_count() const { return error_count_; }
   /// @returns the number of entries in the list.
   size_t count() const { return entries_.size(); }
   /// @returns the first diagnostic in the list.
@@ -99,7 +101,7 @@
 
  private:
   std::vector<Diagnostic> entries_;
-  bool contains_errors_ = false;
+  size_t error_count_ = 0;
 };
 
 }  // namespace diag
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 8e5c0cf..6aab589 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -238,8 +238,17 @@
 // translation_unit
 //  : global_decl* EOF
 void ParserImpl::translation_unit() {
-  while (!peek().IsEof() && synchronized_) {
+  while (synchronized_) {
+    auto p = peek();
+    if (p.IsEof()) {
+      break;
+    }
     expect_global_decl();
+    if (diags_.error_count() >= max_errors_) {
+      add_error(Source{{}, p.source().file},
+                "stopping after " + std::to_string(max_errors_) + " errors");
+      break;
+    }
   }
 
   assert(module_.IsValid());
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index 9b6a841..e533847 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -233,6 +233,15 @@
   /// @returns true if the parse was successful, false otherwise.
   bool Parse();
 
+  /// set_max_diagnostics sets the maximum number of reported errors before
+  /// aborting parsing.
+  /// @param limit the new maximum number of errors
+  void set_max_errors(size_t limit) { max_errors_ = limit; }
+
+  /// @return the number of maximum number of reported errors before aborting
+  /// parsing.
+  size_t get_max_errors() const { return max_errors_; }
+
   /// @returns true if an error was encountered.
   bool has_error() const { return diags_.contains_errors(); }
 
@@ -779,6 +788,7 @@
   int silence_errors_ = 0;
   std::unordered_map<std::string, ast::type::Type*> registered_constructs_;
   ast::Module module_;
+  size_t max_errors_ = 25;
 };
 
 }  // namespace wgsl
diff --git a/src/reader/wgsl/parser_impl_error_msg_test.cc b/src/reader/wgsl/parser_impl_error_msg_test.cc
index 8a53335..d43d60f 100644
--- a/src/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/reader/wgsl/parser_impl_error_msg_test.cc
@@ -28,6 +28,7 @@
     std::string source = SOURCE;                                     \
     std::string expected = EXPECTED;                                 \
     auto p = parser(source);                                         \
+    p->set_max_errors(5);                                            \
     EXPECT_EQ(false, p->Parse());                                    \
     EXPECT_EQ(true, p->diagnostics().contains_errors());             \
     EXPECT_EQ(expected, diag::Formatter().format(p->diagnostics())); \
@@ -1168,6 +1169,26 @@
          "                       ^\n");
 }
 
+TEST_F(ParserImplErrorTest, MaxErrorsReached) {
+  EXPECT("x; x; x; x; x; x; x; x;",
+         "test.wgsl:1:1 error: unexpected token\n"
+         "x; x; x; x; x; x; x; x;\n"
+         "^\n\n"
+         "test.wgsl:1:4 error: unexpected token\n"
+         "x; x; x; x; x; x; x; x;\n"
+         "   ^\n\n"
+         "test.wgsl:1:7 error: unexpected token\n"
+         "x; x; x; x; x; x; x; x;\n"
+         "      ^\n\n"
+         "test.wgsl:1:10 error: unexpected token\n"
+         "x; x; x; x; x; x; x; x;\n"
+         "         ^\n\n"
+         "test.wgsl:1:13 error: unexpected token\n"
+         "x; x; x; x; x; x; x; x;\n"
+         "            ^\n\n"
+         "test.wgsl error: stopping after 5 errors");
+}
+
 TEST_F(ParserImplErrorTest, MemberExprMissingIdentifier) {
   EXPECT("fn f() -> void { x = a.; }",
          "test.wgsl:1:24 error: expected identifier for member accessor\n"