Starlark: fail at range construction time if len exceeds maxint

Alternative to #12675

Closes #12733.

PiperOrigin-RevId: 419959983
diff --git a/src/main/java/net/starlark/java/eval/RangeList.java b/src/main/java/net/starlark/java/eval/RangeList.java
index 2a4c488..c0a6fc8 100644
--- a/src/main/java/net/starlark/java/eval/RangeList.java
+++ b/src/main/java/net/starlark/java/eval/RangeList.java
@@ -30,6 +30,8 @@
  * simple math to compute a value at each index. This is particularly useful when range is huge or
  * only a few elements from it are used.
  *
+ * <p>The start, stop, step, and size of the range must all fit within 32-bit signed integers.
+ *
  * <p>Eventually {@code range} function should produce an instance of the {@code range} type as is
  * the case in Python 3, but for now to preserve backwards compatibility with Python 2, {@code list}
  * is returned.
@@ -56,8 +58,7 @@
   private final int step;
   private final int size; // (derived)
 
-  // TODO(adonovan): use StarlarkInt computation, to avoid overflow.
-  RangeList(int start, int stop, int step) {
+  RangeList(int start, int stop, int step) throws EvalException {
     Preconditions.checkArgument(step != 0);
 
     this.start = start;
@@ -80,8 +81,12 @@
     if (low >= high) {
       this.size = 0;
     } else {
-      int diff = high - low - 1;
-      this.size = diff / step + 1;
+      long diff = (long) high - low - 1;
+      long size = diff / step + 1;
+      if ((int) size != size) {
+        throw Starlark.errorf("len(%s) exceeds signed 32-bit range", Starlark.repr(this));
+      }
+      this.size = (int) size;
     }
   }
 
@@ -171,7 +176,10 @@
   }
 
   @Override
-  public Sequence<StarlarkInt> getSlice(Mutability mu, int start, int stop, int step) {
+  public Sequence<StarlarkInt> getSlice(Mutability mu, int start, int stop, int step)
+      throws EvalException {
+    // TODO(arostovtsev): verify step * this.step doesn't overflow int. Otherwise, we can get a
+    // shorter or even zero step size (and the latter would fail a precondition check).
     return new RangeList(at(start), at(stop), step * this.step);
   }
 
diff --git a/src/main/java/net/starlark/java/eval/Sequence.java b/src/main/java/net/starlark/java/eval/Sequence.java
index 7242f5f..7d9a045 100644
--- a/src/main/java/net/starlark/java/eval/Sequence.java
+++ b/src/main/java/net/starlark/java/eval/Sequence.java
@@ -100,7 +100,7 @@
    * For negative strides ({@code step < 0}), {@code -1 <= stop <= start < size()}. <br>
    * The caller must ensure that the start and stop indices are valid and that step is non-zero.
    */
-  Sequence<E> getSlice(Mutability mu, int start, int stop, int step);
+  Sequence<E> getSlice(Mutability mu, int start, int stop, int step) throws EvalException;
 
   /**
    * Casts a non-null Starlark value {@code x} to a {@code Sequence<T>}, after checking that each
diff --git a/src/main/java/net/starlark/java/eval/StarlarkList.java b/src/main/java/net/starlark/java/eval/StarlarkList.java
index 6fce805..f46ac6a 100644
--- a/src/main/java/net/starlark/java/eval/StarlarkList.java
+++ b/src/main/java/net/starlark/java/eval/StarlarkList.java
@@ -315,7 +315,8 @@
   }
 
   @Override
-  public StarlarkList<E> getSlice(Mutability mu, int start, int stop, int step) {
+  public StarlarkList<E> getSlice(Mutability mu, int start, int stop, int step)
+      throws EvalException {
     RangeList indices = new RangeList(start, stop, step);
     int n = indices.size();
     Object[] res = new Object[n];
diff --git a/src/main/java/net/starlark/java/eval/StringModule.java b/src/main/java/net/starlark/java/eval/StringModule.java
index 0810825..f08113a 100644
--- a/src/main/java/net/starlark/java/eval/StringModule.java
+++ b/src/main/java/net/starlark/java/eval/StringModule.java
@@ -66,7 +66,7 @@
   private StringModule() {}
 
   // Returns s[start:stop:step], as if by Sequence.getSlice.
-  static String slice(String s, int start, int stop, int step) {
+  static String slice(String s, int start, int stop, int step) throws EvalException {
     RangeList indices = new RangeList(start, stop, step);
     int n = indices.size();
     if (n == 0) {
diff --git a/src/main/java/net/starlark/java/eval/Tuple.java b/src/main/java/net/starlark/java/eval/Tuple.java
index c6c6d66..f4429969 100644
--- a/src/main/java/net/starlark/java/eval/Tuple.java
+++ b/src/main/java/net/starlark/java/eval/Tuple.java
@@ -226,7 +226,7 @@
   }
 
   @Override
-  public Tuple getSlice(Mutability mu, int start, int stop, int step) {
+  public Tuple getSlice(Mutability mu, int start, int stop, int step) throws EvalException {
     RangeList indices = new RangeList(start, stop, step);
     int n = indices.size();
     if (step == 1) { // common case
diff --git a/src/test/java/net/starlark/java/eval/testdata/range.star b/src/test/java/net/starlark/java/eval/testdata/range.star
index 32c4aa4..8622f32 100644
--- a/src/test/java/net/starlark/java/eval/testdata/range.star
+++ b/src/test/java/net/starlark/java/eval/testdata/range.star
@@ -13,3 +13,13 @@
 assert_eq(list(range(5, 0, -10)), [5])
 assert_eq(list(range(0, -3, -2)), [0, -2])
 assert_fails(lambda: range(2, 3, 0), "step cannot be 0")
+
+maxint = (1 << 31) - 1
+minint = (-1) << 31
+
+assert_eq(maxint, len(range(0, maxint)))
+assert_eq(maxint, len(range(minint, maxint - 1, 2)))
+assert_fails(lambda: range(-1, maxint), "len\\(range\\(-1, 2147483647\\)\\) exceeds signed 32-bit range")
+assert_fails(lambda: range(minint - 1, minint), "got -2147483649 for start, want value in signed 32-bit range")
+assert_fails(lambda: range(maxint, maxint + 1), "got 2147483648 for stop, want value in signed 32-bit range")
+assert_fails(lambda: range(-1, maxint, maxint + 1), "got 2147483648 for step, want value in signed 32-bit range")