Create --debug_depset_depth debug mode

This debug mode will throw an exception on Starlark invocations to the depset constructor when the constructed depset would exceed the application depth limit (set by `--nested_set_depth_limit`). This mode effectively flattens each depset on creation, which is overall a *very* expensive operation, and should thus be done only for debugging purposes.

RELNOTES: None.
PiperOrigin-RevId: 281354629
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
index b0e2dc2..5f35aed 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
@@ -64,6 +64,19 @@
   // <== Add new options here in alphabetic order ==>
 
   @Option(
+      name = "debug_depset_depth",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
+      effectTags = {OptionEffectTag.BUILD_FILE_SEMANTICS},
+      metadataTags = {},
+      help =
+          "Enables an expensive additional check that causes depset construction to fail fast if "
+              + "the depset's depth would exceed the limit specified by "
+              + "`--nested_set_depth_limit`. Ordinarily this failure occurs only when the depset "
+              + "is flattened, which may be far from its point of creation.")
+  public boolean debugDepsetDepth;
+
+  @Option(
       name = "experimental_action_args",
       defaultValue = "false",
       documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
@@ -621,6 +634,7 @@
     StarlarkSemantics semantics =
         StarlarkSemantics.builder()
             // <== Add new options here in alphabetic order ==>
+            .debugDepsetDepth(debugDepsetDepth)
             .experimentalActionArgs(experimentalActionArgs)
             .experimentalAllowIncrementalRepositoryUpdates(
                 experimentalAllowIncrementalRepositoryUpdates)
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
index e0b267d..b8f4332 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
@@ -1011,6 +1011,7 @@
       StarlarkSemantics semantics)
       throws EvalException {
     Order order;
+    SkylarkNestedSet result;
     try {
       order = Order.parse(orderString);
     } catch (IllegalArgumentException ex) {
@@ -1025,7 +1026,7 @@
         }
         direct = x;
       }
-      return depsetConstructor(direct, order, transitive, loc);
+      result = depsetConstructor(direct, order, transitive, loc);
     } else {
       if (x != Starlark.NONE) {
         if (!isEmptySkylarkList(items)) {
@@ -1034,8 +1035,21 @@
         }
         items = x;
       }
-      return legacyDepsetConstructor(items, order, direct, transitive, loc);
+      result = legacyDepsetConstructor(items, order, direct, transitive, loc);
     }
+
+    if (semantics.debugDepsetDepth()) {
+      // Flatten the underlying nested set. If the set exceeds the depth limit, then this will
+      // throw a NestedSetDepthException.
+      // This is an extremely inefficient check and should be only done in the
+      // "--debug_depset_depth" mode.
+      try {
+        result.getSet().toList();
+      } catch (NestedSetDepthException ex) {
+        throw new EvalException(null, "depset exceeded maximum depth " + ex.getDepthLimit());
+      }
+    }
+    return result;
   }
 
   private static SkylarkNestedSet depsetConstructor(
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
index 81ab452..7395bfc 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
@@ -131,6 +131,8 @@
       AutoValue_StarlarkSemantics.class;
 
   // <== Add new options here in alphabetic order ==>
+  public abstract boolean debugDepsetDepth();
+
   public abstract boolean experimentalActionArgs();
 
   public abstract boolean experimentalAllowIncrementalRepositoryUpdates();
@@ -246,6 +248,7 @@
   public static final StarlarkSemantics DEFAULT_SEMANTICS =
       builder()
           // <== Add new options here in alphabetic order ==>
+          .debugDepsetDepth(false)
           .experimentalActionArgs(false)
           .experimentalAllowTagsPropagation(false)
           .experimentalAspectOutputPropagation(false)
@@ -295,6 +298,8 @@
   public abstract static class Builder {
 
     // <== Add new options here in alphabetic order ==>
+    public abstract Builder debugDepsetDepth(boolean value);
+
     public abstract Builder experimentalActionArgs(boolean value);
 
     public abstract Builder experimentalAllowIncrementalRepositoryUpdates(boolean value);
diff --git a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
index 63ad8c8..11f9814 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
@@ -118,6 +118,7 @@
   private static StarlarkSemanticsOptions buildRandomOptions(Random rand) throws Exception {
     return parseOptions(
         // <== Add new options here in alphabetic order ==>
+        "--debug_depset_depth=" + rand.nextBoolean(),
         "--experimental_action_args=" + rand.nextBoolean(),
         "--experimental_allow_incremental_repository_updates=" + rand.nextBoolean(),
         "--experimental_aspect_output_propagation=" + rand.nextBoolean(),
@@ -171,6 +172,7 @@
   private static StarlarkSemantics buildRandomSemantics(Random rand) {
     return StarlarkSemantics.builder()
         // <== Add new options here in alphabetic order ==>
+        .debugDepsetDepth(rand.nextBoolean())
         .experimentalActionArgs(rand.nextBoolean())
         .experimentalAllowIncrementalRepositoryUpdates(rand.nextBoolean())
         .experimentalAspectOutputPropagation(rand.nextBoolean())
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
index b289aa1..04cd324 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
@@ -806,4 +806,18 @@
         .testIfErrorContains("depset exceeded maximum depth 2000", "str(too_deep_depset)")
         .testIfErrorContains("depset exceeded maximum depth 2000", "too_deep_depset.to_list()");
   }
+
+  @Test
+  public void testDepsetDebugDepth() throws Exception {
+    NestedSet.setApplicationDepthLimit(2000);
+    new SkylarkTest("--debug_depset_depth=true")
+        .setUp(
+            "def create_depset(depth):",
+            "  x = depset([0])",
+            "  for i in range(1, depth):",
+            "    x = depset([i], transitive = [x])",
+            "  return x")
+        .testEval("str(create_depset(900))[0:6]", "'depset'")
+        .testIfErrorContains("depset exceeded maximum depth 2000", "create_depset(3000)");
+  }
 }