resolver: Fix bug in dep graph SortGlobals()

If we encounter a global twice, the stack vector was not popped, which could trigger false cyclic-dependency errors. Because the cyclic dependency did not actually exist, later logic could break, expecting to find a global->global edge which did not exist.

Fixed: chromium:1273451
Change-Id: I9d99f1aeeaea042d9ed847a878c4717803122240
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/70980
Reviewed-by: David Neto <dneto@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/resolver/dependency_graph.cc b/src/resolver/dependency_graph.cc
index 4cfb45d..947be99 100644
--- a/src/resolver/dependency_graph.cc
+++ b/src/resolver/dependency_graph.cc
@@ -305,8 +305,8 @@
             auto global_it = globals_.find(ident->symbol);
             if (global_it != globals_.end() &&
                 node == global_it->second->node) {
-              ResolveGlobalDependency(ident, ident->symbol, "identifier",
-                                      "references");
+              AddGlobalDependency(ident, ident->symbol, "identifier",
+                                  "references");
             } else {
               graph_.resolved_symbols.emplace(ident, node);
             }
@@ -314,9 +314,9 @@
           if (auto* call = expr->As<ast::CallExpression>()) {
             if (call->target.name) {
               if (!IsIntrinsic(call->target.name->symbol)) {
-                ResolveGlobalDependency(call->target.name,
-                                        call->target.name->symbol, "function",
-                                        "calls");
+                AddGlobalDependency(call->target.name,
+                                    call->target.name->symbol, "function",
+                                    "calls");
                 graph_.resolved_symbols.emplace(
                     call,
                     utils::Lookup(graph_.resolved_symbols, call->target.name));
@@ -357,7 +357,7 @@
       return;
     }
     if (auto* tn = ty->As<ast::TypeName>()) {
-      ResolveGlobalDependency(tn, tn->name, "type", "references");
+      AddGlobalDependency(tn, tn->name, "type", "references");
       return;
     }
     if (auto* vec = ty->As<ast::Vector>()) {
@@ -415,10 +415,10 @@
   }
 
   /// Adds the dependency to the currently processed global
-  void ResolveGlobalDependency(const ast::Node* from,
-                               Symbol to,
-                               const char* use,
-                               const char* action) {
+  void AddGlobalDependency(const ast::Node* from,
+                           Symbol to,
+                           const char* use,
+                           const char* action) {
     auto global_it = globals_.find(to);
     if (global_it != globals_.end()) {
       auto* global = global_it->second;
@@ -482,9 +482,8 @@
     // Sort the globals into dependency order
     SortGlobals();
 
-#if TINT_DUMP_DEPENDENCY_GRAPH
+    // Dump the dependency graph if TINT_DUMP_DEPENDENCY_GRAPH is non-zero
     DumpDependencyGraph();
-#endif
 
     if (!allow_out_of_order_decls) {
       // Prevent out-of-order declarations.
@@ -630,16 +629,26 @@
               return false;
             }
             if (sorted_.contains(g->node)) {
-              return false;  // Visited this global already.
+              // Visited this global already.
+              // stack was pushed, but exit() will not be called when we return
+              // false, so pop here.
+              stack.pop_back();
+              return false;
             }
             return true;
           },
-          [&](const Global* g) {  // Exit
+          [&](const Global* g) {  // Exit. Only called if Enter returned true.
             sorted_.add(g->node);
             stack.pop_back();
           });
 
       sorted_.add(global->node);
+
+      if (!stack.empty()) {
+        // Each stack.push() must have a corresponding stack.pop_back().
+        TINT_ICE(Resolver, diagnostics_)
+            << "stack not empty after returning from TraverseDependencies()";
+      }
     }
   }
 
@@ -720,25 +729,28 @@
     }
   }
 
-#if TINT_DUMP_DEPENDENCY_GRAPH
   void DumpDependencyGraph() {
+#if TINT_DUMP_DEPENDENCY_GRAPH == 0
+    if ((true)) {
+      return;
+    }
+#endif  // TINT_DUMP_DEPENDENCY_GRAPH
     printf("=========================\n");
     printf("------ declaration ------ \n");
     for (auto* global : declaration_order_) {
       printf("%s\n", NameOf(global->node).c_str());
     }
     printf("------ dependencies ------ \n");
-    for (auto* node : sorted) {
+    for (auto* node : sorted_) {
       auto symbol = SymbolOf(node);
-      auto* global = globals.at(symbol);
-      printf("%s depends on:\n", symbols.NameFor(symbol).c_str());
-      for (auto& dep : global->deps) {
-        printf("  %s\n", NameOf(dep.global->node).c_str());
+      auto* global = globals_.at(symbol);
+      printf("%s depends on:\n", symbols_.NameFor(symbol).c_str());
+      for (auto* dep : global->deps) {
+        printf("  %s\n", NameOf(dep->node).c_str());
       }
     }
     printf("=========================\n");
   }
-#endif  // TINT_DUMP_DEPENDENCY_GRAPH
 
   /// Program symbols
   const SymbolTable& symbols_;
diff --git a/src/resolver/dependency_graph_test.cc b/src/resolver/dependency_graph_test.cc
index e222b43..5fd048d 100644
--- a/src/resolver/dependency_graph_test.cc
+++ b/src/resolver/dependency_graph_test.cc
@@ -1339,6 +1339,19 @@
   Build();
 }
 
+// Reproduces an unbalanced stack push / pop bug in
+// DependencyAnalysis::SortGlobals(), found by clusterfuzz.
+// See: crbug.com/chromium/1273451
+TEST_F(ResolverDependencyGraphTraversalTest, chromium_1273451) {
+  Structure("A", {Member("a", ty.i32())});
+  Structure("B", {Member("b", ty.i32())});
+  Func("f", {Param("a", ty.type_name("A"))}, ty.type_name("B"),
+       {
+           Return(Construct(ty.type_name("B"))),
+       });
+  Build();
+}
+
 }  // namespace ast_traversal
 
 }  // namespace
diff --git a/src/utils/unique_vector.h b/src/utils/unique_vector.h
index 5cb754d..f64ed07 100644
--- a/src/utils/unique_vector.h
+++ b/src/utils/unique_vector.h
@@ -71,6 +71,9 @@
   /// @returns the element at the index `i`
   const T& operator[](size_t i) const { return vector[i]; }
 
+  /// @returns true if the vector is empty
+  size_t empty() const { return vector.empty(); }
+
   /// @returns the number of items in the vector
   size_t size() const { return vector.size(); }
 
diff --git a/src/utils/unique_vector_test.cc b/src/utils/unique_vector_test.cc
index fc16784..3ec78fa 100644
--- a/src/utils/unique_vector_test.cc
+++ b/src/utils/unique_vector_test.cc
@@ -24,12 +24,14 @@
 TEST(UniqueVectorTest, Empty) {
   UniqueVector<int> unique_vec;
   EXPECT_EQ(unique_vec.size(), 0u);
+  EXPECT_EQ(unique_vec.empty(), true);
   EXPECT_EQ(unique_vec.begin(), unique_vec.end());
 }
 
 TEST(UniqueVectorTest, MoveConstructor) {
   UniqueVector<int> unique_vec(std::vector<int>{0, 3, 2, 1, 2});
   EXPECT_EQ(unique_vec.size(), 4u);
+  EXPECT_EQ(unique_vec.empty(), false);
   EXPECT_EQ(unique_vec[0], 0);
   EXPECT_EQ(unique_vec[1], 3);
   EXPECT_EQ(unique_vec[2], 2);
@@ -42,6 +44,7 @@
   unique_vec.add(1);
   unique_vec.add(2);
   EXPECT_EQ(unique_vec.size(), 3u);
+  EXPECT_EQ(unique_vec.empty(), false);
   int i = 0;
   for (auto n : unique_vec) {
     EXPECT_EQ(n, i);
@@ -65,6 +68,7 @@
   unique_vec.add(1);
   unique_vec.add(2);
   EXPECT_EQ(unique_vec.size(), 3u);
+  EXPECT_EQ(unique_vec.empty(), false);
   int i = 0;
   for (auto n : unique_vec) {
     EXPECT_EQ(n, i);
@@ -90,6 +94,7 @@
 
   const std::vector<int>& vec = unique_vec;
   EXPECT_EQ(vec.size(), 3u);
+  EXPECT_EQ(unique_vec.empty(), false);
   int i = 0;
   for (auto n : vec) {
     EXPECT_EQ(n, i);
@@ -109,18 +114,30 @@
 
   EXPECT_EQ(unique_vec.pop_back(), 1);
   EXPECT_EQ(unique_vec.size(), 2u);
+  EXPECT_EQ(unique_vec.empty(), false);
   EXPECT_EQ(unique_vec[0], 0);
   EXPECT_EQ(unique_vec[1], 2);
 
   EXPECT_EQ(unique_vec.pop_back(), 2);
   EXPECT_EQ(unique_vec.size(), 1u);
+  EXPECT_EQ(unique_vec.empty(), false);
   EXPECT_EQ(unique_vec[0], 0);
 
   unique_vec.add(1);
 
   EXPECT_EQ(unique_vec.size(), 2u);
+  EXPECT_EQ(unique_vec.empty(), false);
   EXPECT_EQ(unique_vec[0], 0);
   EXPECT_EQ(unique_vec[1], 1);
+
+  EXPECT_EQ(unique_vec.pop_back(), 1);
+  EXPECT_EQ(unique_vec.size(), 1u);
+  EXPECT_EQ(unique_vec.empty(), false);
+  EXPECT_EQ(unique_vec[0], 0);
+
+  EXPECT_EQ(unique_vec.pop_back(), 0);
+  EXPECT_EQ(unique_vec.size(), 0u);
+  EXPECT_EQ(unique_vec.empty(), true);
 }
 
 }  // namespace
diff --git a/test/bug/chromium/1273451.wgsl b/test/bug/chromium/1273451.wgsl
new file mode 100644
index 0000000..2289c10
--- /dev/null
+++ b/test/bug/chromium/1273451.wgsl
@@ -0,0 +1,11 @@
+struct A {
+  a : i32;
+};
+
+struct B {
+  b : i32;
+};
+
+fn f(a : A) -> B {
+  return B();
+}