Fix NPE in the Starlark debugger

If we breakpoint at a line before a local (that is a cell) has been intialized, cell.x is null, which we shouldn't attempt to add to the returned map of locals.

Fixes https://github.com/bazelbuild/bazel/issues/24339

PiperOrigin-RevId: 698316990
Change-Id: Ifb45650fb41471c47605292af9873e9f66b7f7d7
diff --git a/src/main/java/net/starlark/java/eval/StarlarkThread.java b/src/main/java/net/starlark/java/eval/StarlarkThread.java
index 18f85c3..b073a8b 100644
--- a/src/main/java/net/starlark/java/eval/StarlarkThread.java
+++ b/src/main/java/net/starlark/java/eval/StarlarkThread.java
@@ -187,10 +187,10 @@
       if (fn instanceof StarlarkFunction) {
         for (int i = 0; i < locals.length; i++) {
           Object local = locals[i];
+          if (local instanceof StarlarkFunction.Cell) {
+            local = ((StarlarkFunction.Cell) local).x;
+          }
           if (local != null) {
-            if (local instanceof StarlarkFunction.Cell) {
-              local = ((StarlarkFunction.Cell) local).x;
-            }
             env.put(((StarlarkFunction) fn).rfn.getLocals().get(i).getName(), local);
           }
         }
diff --git a/src/test/java/com/google/devtools/build/lib/starlarkdebug/server/StarlarkDebugServerTest.java b/src/test/java/com/google/devtools/build/lib/starlarkdebug/server/StarlarkDebugServerTest.java
index 878f1b5..8648802 100644
--- a/src/test/java/com/google/devtools/build/lib/starlarkdebug/server/StarlarkDebugServerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlarkdebug/server/StarlarkDebugServerTest.java
@@ -413,6 +413,119 @@
   }
 
   @Test
+  public void testListFramesWithUninitializedCellRequest() throws Exception {
+    sendStartDebuggingRequest();
+    ParserInput bzlFile =
+        createInput(
+            "/a/build/file/foo.bzl",
+            """
+            def outer():
+               x = [1,2,3] # <- breakpoint
+
+               def inner():
+                   x.append(4)
+
+               inner()
+
+            outer()
+            """);
+
+    Location breakpoint =
+        Location.newBuilder().setLineNumber(2).setPath("/a/build/file/foo.bzl").build();
+    setBreakpoints(ImmutableList.of(breakpoint));
+
+    Module module = Module.create();
+    Thread evaluationThread = execInWorkerThread(bzlFile, module);
+    long threadId = evaluationThread.getId();
+
+    // wait for breakpoint to be hit
+    client.waitForEvent(DebugEvent::hasThreadPaused, Duration.ofSeconds(5));
+
+    ListFramesResponse frames = listFrames(threadId);
+    assertThat(frames.getFrameCount()).isEqualTo(2);
+    // critically x is not present as a local
+    assertFramesEqualIgnoringValueIdentifiers(
+        frames.getFrame(0),
+        Frame.newBuilder()
+            .setFunctionName("outer")
+            .setLocation(breakpoint.toBuilder().setColumnNumber(4))
+            .addScope(
+                Scope.newBuilder()
+                    .setName("global")
+                    .addBinding(getValueProto("outer", module.getGlobal("outer"))))
+            .build());
+    assertFramesEqualIgnoringValueIdentifiers(
+        frames.getFrame(1),
+        Frame.newBuilder()
+            .setFunctionName(StarlarkThread.TOP_LEVEL)
+            .setLocation(breakpoint.toBuilder().setLineNumber(9).setColumnNumber(6))
+            .addScope(
+                Scope.newBuilder()
+                    .setName("global")
+                    .addBinding(getValueProto("outer", module.getGlobal("outer"))))
+            .build());
+  }
+
+  @Test
+  public void testListFramesWithInitializedCellRequest() throws Exception {
+    sendStartDebuggingRequest();
+    ParserInput bzlFile =
+        createInput(
+            "/a/build/file/foo.bzl",
+            """
+            def outer():
+               x = [1]
+               pass          # <- breakpoint
+
+               def inner():
+                   x.append(4)
+
+               inner()
+
+            outer()
+            """);
+
+    Location breakpoint =
+        Location.newBuilder().setLineNumber(3).setPath("/a/build/file/foo.bzl").build();
+    setBreakpoints(ImmutableList.of(breakpoint));
+
+    Module module = Module.create();
+    Thread evaluationThread = execInWorkerThread(bzlFile, module);
+    long threadId = evaluationThread.getId();
+
+    // wait for breakpoint to be hit
+    client.waitForEvent(DebugEvent::hasThreadPaused, Duration.ofSeconds(5));
+
+    ListFramesResponse frames = listFrames(threadId);
+    assertThat(frames.getFrameCount()).isEqualTo(2);
+    // critically x is present as a local
+    assertFramesEqualIgnoringValueIdentifiers(
+        frames.getFrame(0),
+        Frame.newBuilder()
+            .setFunctionName("outer")
+            .setLocation(breakpoint.toBuilder().setColumnNumber(4))
+            .addScope(
+                Scope.newBuilder()
+                    .setName("local")
+                    .addBinding(getValueProto("x", StarlarkList.immutableOf(StarlarkInt.of(1)))))
+            .addScope(
+                Scope.newBuilder()
+                    .setName("global")
+                    .addBinding(getValueProto("outer", module.getGlobal("outer"))))
+            .build());
+    assertFramesEqualIgnoringValueIdentifiers(
+        frames.getFrame(1),
+        Frame.newBuilder()
+            .setFunctionName(StarlarkThread.TOP_LEVEL)
+            .setLocation(breakpoint.toBuilder().setLineNumber(10).setColumnNumber(6))
+            .addScope(
+                Scope.newBuilder()
+                    .setName("global")
+                    .addBinding(getValueProto("outer", module.getGlobal("outer"))))
+            .build());
+  }
+
+  @Test
   public void testGetChildrenRequest() throws Exception {
     sendStartDebuggingRequest();
     ParserInput buildFile =