Disallow mutation of values being iterated by a for loop or comprehension

This entails adding a read-locking mechanism to Mutability contexts.

RELNOTES[INC]: Updating list/dicts while they are being looped over is not allowed. Use an explicit copy if needed ("for x in list(mylist):").

--
MOS_MIGRATED_REVID=134442701
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
index b7650e0..81b6173 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
@@ -357,37 +357,93 @@
 
   @Test
   public void testForUpdateList() throws Exception {
-    // Check that modifying the list within the loop doesn't affect what gets iterated over.
-    // TODO(brandjon): When we support in-place assignment to a list index, also test that
-    // as a modification here.
     new SkylarkTest().setUp("def foo():",
-        "  xs = ['a', 'b', 'c']",
-        "  s = ''",
+        "  xs = [1, 2, 3]",
         "  for x in xs:",
-        "    s = s + x",
-        "    if x == 'a':",
-        "      xs.pop(0)",
-        "      xs.pop(1)",
-        "    elif x == 'c':",
-        "      xs.append('d')",
-        "  return s",
-        "s = foo()").testLookup("s", "abc");
+        "    if x == 1:",
+        "      xs.append(10)"
+        ).testIfErrorContains("trying to mutate a locked object", "foo()");
   }
 
   @Test
   public void testForUpdateDict() throws Exception {
-    // Check that modifying the dict within the loop doesn't affect what gets iterated over.
     new SkylarkTest().setUp("def foo():",
         "  d = {'a': 1, 'b': 2, 'c': 3}",
-        "  s = ''",
         "  for k in d:",
-        "    s = s + k",
-        "    if k == 'a':",
-        "      d.pop('a')",
-        "      d.pop('b')",
-        "    d.update({'d': 4})",
-        "  return s",
-        "s = foo()").testLookup("s", "abc");
+        "    d[k] *= 2"
+        ).testIfErrorContains("trying to mutate a locked object", "foo()");
+  }
+
+  @Test
+  public void testForUnlockedAfterBreak() throws Exception {
+    new SkylarkTest().setUp("def foo():",
+        "  xs = [1, 2]",
+        "  for x in xs:",
+        "    break",
+        "  xs.append(3)",
+        "  return xs"
+        ).testEval("foo()", "[1, 2, 3]");
+  }
+
+  @Test
+  public void testForNestedOnSameListStillLocked() throws Exception {
+    new SkylarkTest().setUp("def foo():",
+        "  xs = [1, 2]",
+        "  ys = []",
+        "  for x1 in xs:",
+        "    for x2 in xs:",
+        "      ys.append(x1 * x2)",
+        "    xs.append(4)",
+        "  return ys"
+        ).testIfErrorContains("trying to mutate a locked object", "foo()");
+  }
+
+  @Test
+  public void testForNestedOnSameListErrorMessage() throws Exception {
+    new SkylarkTest().setUp("def foo():",
+        "  xs = [1, 2]",
+        "  ys = []",
+        "  for x1 in xs:",
+        "    for x2 in xs:",
+        "      ys.append(x1 * x2)",
+        "      xs.append(4)",
+        "  return ys"
+        // No file name in message, due to how test is set up.
+        ).testIfErrorContains("Object locked at the following locations: 4:3, 5:5", "foo()");
+  }
+
+  @Test
+  public void testForNestedOnSameListUnlockedAtEnd() throws Exception {
+    new SkylarkTest().setUp("def foo():",
+        "  xs = [1, 2]",
+        "  ys = []",
+        "  for x1 in xs:",
+        "    for x2 in xs:",
+        "      ys.append(x1 * x2)",
+        "  xs.append(4)",
+        "  return ys"
+        ).testEval("foo()", "[1, 2, 2, 4]");
+  }
+
+  @Test
+  public void testForNestedWithListCompGood() throws Exception {
+    new SkylarkTest().setUp("def foo():",
+        "  xs = [1, 2]",
+        "  ys = []",
+        "  for x in xs:",
+        "    zs = [None for x in xs for y in (ys.append(x) or ys)]",
+        "  return ys"
+        ).testEval("foo()", "[1, 2, 1, 2]");
+  }
+  @Test
+  public void testForNestedWithListCompBad() throws Exception {
+    new SkylarkTest().setUp("def foo():",
+        "  xs = [1, 2, 3]",
+        "  ys = []",
+        "  for x in xs:",
+        "    zs = [None for x in xs for y in (xs.append(x) or ys)]",
+        "  return ys"
+        ).testIfErrorContains("trying to mutate a locked object", "foo()");
   }
 
   @Test