Fix stack-overflow in `lhs_expression`.

Currently when parsing `*` and `&` we recursively call into ourselves to
process the tokens. This can cause stack issues if there are an
excessive number of `*`s and `&`s.

This Cl changes `lhs_expression` to generate a list of UnaryOps to be
applied and does not recursively call `lhs_expression`.

Bug: chromium:1394972
Change-Id: I40caee05c9b7f71abb776d375cbf995c6a1fd36f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/112580
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index 49e266f..a04e548 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -43,6 +43,7 @@
 #include "src/tint/sem/external_texture.h"
 #include "src/tint/sem/multisampled_texture.h"
 #include "src/tint/sem/sampled_texture.h"
+#include "src/tint/utils/reverse.h"
 #include "src/tint/utils/string.h"
 
 namespace tint::reader::wgsl {
@@ -3239,34 +3240,50 @@
         return component_or_swizzle_specifier(core_expr.value);
     }
 
-    auto check_lhs = [&](ast::UnaryOp op) -> Maybe<const ast::Expression*> {
-        auto& t = peek();
-        auto expr = lhs_expression();
-        if (expr.errored) {
-            return Failure::kErrored;
-        }
-        if (!expr.matched) {
-            return add_error(t, "missing expression");
-        }
-        return create<ast::UnaryOpExpression>(t.source(), op, expr.value);
+    // Gather up all the `*`, `&` and `&&` tokens into a list and create all of the unary ops at
+    // once instead of recursing. This handles the case where the fuzzer decides >8k `*`s would be
+    // fun.
+    struct LHSData {
+        Source source;
+        ast::UnaryOp op;
     };
+    utils::Vector<LHSData, 4> ops;
+    while (true) {
+        auto& t = peek();
+        if (!t.Is(Token::Type::kAndAnd) && !t.Is(Token::Type::kAnd) && !t.Is(Token::Type::kStar)) {
+            break;
+        }
+        next();  // consume the peek
 
-    // If an `&&` is encountered, split it into two `&`'s
-    if (match(Token::Type::kAndAnd)) {
-        // The first `&` is consumed as part of the `&&`, so this needs to run the check itself.
-        split_token(Token::Type::kAnd, Token::Type::kAnd);
-        return check_lhs(ast::UnaryOp::kAddressOf);
+        if (t.Is(Token::Type::kAndAnd)) {
+            // The first `&` is consumed as part of the `&&`, so we only push one of the two `&`s.
+            split_token(Token::Type::kAnd, Token::Type::kAnd);
+            ops.Push({t.source(), ast::UnaryOp::kAddressOf});
+        } else if (t.Is(Token::Type::kAnd)) {
+            ops.Push({t.source(), ast::UnaryOp::kAddressOf});
+        } else if (t.Is(Token::Type::kStar)) {
+            ops.Push({t.source(), ast::UnaryOp::kIndirection});
+        }
+    }
+    if (ops.IsEmpty()) {
+        return Failure::kNoMatch;
     }
 
-    if (match(Token::Type::kAnd)) {
-        return check_lhs(ast::UnaryOp::kAddressOf);
+    auto& t = peek();
+    auto expr = lhs_expression();
+    if (expr.errored) {
+        return Failure::kErrored;
+    }
+    if (!expr.matched) {
+        return add_error(t, "missing expression");
     }
 
-    if (match(Token::Type::kStar)) {
-        return check_lhs(ast::UnaryOp::kIndirection);
+    const ast::Expression* ret = expr.value;
+    // Consume the ops in reverse order so we have the correct AST ordering.
+    for (auto& info : utils::Reverse(ops)) {
+        ret = create<ast::UnaryOpExpression>(info.source, info.op, ret);
     }
-
-    return Failure::kNoMatch;
+    return ret;
 }
 
 // variable_updating_statement
diff --git a/src/tint/reader/wgsl/parser_impl_lhs_expression_test.cc b/src/tint/reader/wgsl/parser_impl_lhs_expression_test.cc
index 4e1e0db..a8a476b 100644
--- a/src/tint/reader/wgsl/parser_impl_lhs_expression_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_lhs_expression_test.cc
@@ -75,17 +75,20 @@
 }
 
 TEST_F(ParserImplTest, LHSExpression_Multiple) {
-    auto p = parser("*&**&&*a");
+    auto p = parser("*&********&&&&&&*a");
     auto e = p->lhs_expression();
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(e.errored);
     EXPECT_TRUE(e.matched);
     ASSERT_NE(e.value, nullptr);
 
-    std::vector<ast::UnaryOp> results = {ast::UnaryOp::kIndirection, ast::UnaryOp::kAddressOf,
-                                         ast::UnaryOp::kIndirection, ast::UnaryOp::kIndirection,
-                                         ast::UnaryOp::kAddressOf,   ast::UnaryOp::kAddressOf,
-                                         ast::UnaryOp::kIndirection};
+    std::vector<ast::UnaryOp> results = {
+        ast::UnaryOp::kIndirection, ast::UnaryOp::kAddressOf,   ast::UnaryOp::kIndirection,
+        ast::UnaryOp::kIndirection, ast::UnaryOp::kIndirection, ast::UnaryOp::kIndirection,
+        ast::UnaryOp::kIndirection, ast::UnaryOp::kIndirection, ast::UnaryOp::kIndirection,
+        ast::UnaryOp::kIndirection, ast::UnaryOp::kAddressOf,   ast::UnaryOp::kAddressOf,
+        ast::UnaryOp::kAddressOf,   ast::UnaryOp::kAddressOf,   ast::UnaryOp::kAddressOf,
+        ast::UnaryOp::kAddressOf,   ast::UnaryOp::kIndirection};
 
     auto* expr = e.value;
     for (auto op : results) {