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(); +}