GN: Don't crash when mutating the list in a foreach.

Avoid a crash caused by mutation of the list being iterated over from inside
the foreach loop. This does a full copy of the the iterated list since the
code inside can't mutate the array via the loop variable anyway. Although
theoretically slower, this doesn't seem to have a measurable performance
regression in practice (we generally iterate over few large lists).

Adds documentation and tests for iteration while mutating the underlying list
variable.

Bug: 818525
Change-Id: I221fa230685b8998f5874154cad8d5c655b8006c
Reviewed-on: https://chromium-review.googlesource.com/959228
Commit-Queue: Brett Wilson <brettw@chromium.org>
Reviewed-by: Roland McGrath <mcgrathr@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#544209}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: dcba727e7e8b00cb26c4d797d956d48d2e98371f
diff --git a/tools/gn/function_foreach.cc b/tools/gn/function_foreach.cc
index e94fbe6..b0af694 100644
--- a/tools/gn/function_foreach.cc
+++ b/tools/gn/function_foreach.cc
@@ -4,7 +4,6 @@
 
 #include "tools/gn/err.h"
 #include "tools/gn/functions.h"
-#include "tools/gn/parse_node_value_adapter.h"
 #include "tools/gn/parse_tree.h"
 #include "tools/gn/scope.h"
 
@@ -21,8 +20,9 @@
     }
 
   Executes the loop contents block over each item in the list, assigning the
-  loop_var to each item in sequence. The loop_var will be a copy so assigning
-  to it will not mutate the list.
+  loop_var to each item in sequence. The <loop_var> will be a copy so assigning
+  to it will not mutate the list. The loop will iterate over a copy of <list>
+  so mutating it inside the loop will not affect iteration.
 
   The block does not introduce a new scope, so that variable assignments inside
   the loop will be visible once the loop terminates.
@@ -65,11 +65,15 @@
   }
   base::StringPiece loop_var(identifier->value().value());
 
-  // Extract the list to iterate over.
-  ParseNodeValueAdapter list_adapter;
-  if (!list_adapter.InitForType(scope, args_vector[1].get(), Value::LIST, err))
+  // Extract the list to iterate over. Always copy in case the code changes
+  // the list variable inside the loop.
+  Value list_value = args_vector[1]->Execute(scope, err);
+  if (err->has_error())
     return Value();
-  const std::vector<Value>& list = list_adapter.get().list_value();
+  list_value.VerifyTypeIs(Value::Type::LIST, err);
+  if (err->has_error())
+    return Value();
+  const std::vector<Value>& list = list_value.list_value();
 
   // Block to execute.
   const BlockNode* block = function->block();
diff --git a/tools/gn/function_foreach_unittest.cc b/tools/gn/function_foreach_unittest.cc
index 462d714..3886724 100644
--- a/tools/gn/function_foreach_unittest.cc
+++ b/tools/gn/function_foreach_unittest.cc
@@ -73,3 +73,28 @@
   EXPECT_TRUE(setup.scope()->CheckForUnusedVars(&err));
   EXPECT_FALSE(err.has_error());
 }
+
+// Checks that the list can be modified during iteration without crashing.
+TEST(FunctionForeach, ListModification) {
+  TestWithScope setup;
+  TestParseInput input_grow(
+      "a = [1, 2]\n"
+      "foreach(i, a) {\n"
+      "  print(i)\n"
+      "  if (i <= 8) {\n"
+      "    a += [ i + 2 ]\n"
+      "  }\n"
+      "}\n"
+      "print(a)");
+  ASSERT_FALSE(input_grow.has_error());
+
+  Err err;
+  input_grow.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+
+  // The result of the loop should have been unaffected by the mutations of
+  // the list variable inside the loop, but the modifications made to it
+  // should have been persisted.
+  EXPECT_EQ("1\n2\n[1, 2, 3, 4]\n", setup.print_output());
+  setup.print_output().clear();
+}