Initial implementation of a Skylark debug server API.

I've pulled out the API for separate review. It includes all
hooks from blaze/skylark used by the debugger.

Debuggable thread contexts are currently declared in 3 places:
- BuildFileAST (top-level evaluation of BUILD files)
- SkylarkRuleConfiguredTargetUtil (rules)
- SkylarkAspectFactory (aspects)

The purpose of declaring these contexts is so that the debugger
can track currently-active threads (and stop tracking them when
the task is completed).

Details of the actual debugging server are in unknown commit.

PiperOrigin-RevId: 197770547
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BuildFileAST.java b/src/main/java/com/google/devtools/build/lib/syntax/BuildFileAST.java
index 439e4c5..ca6307f 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BuildFileAST.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BuildFileAST.java
@@ -193,6 +193,18 @@
    * @return true if no error occurred during execution.
    */
   public boolean exec(Environment env, EventHandler eventHandler) throws InterruptedException {
+    try {
+      // Handles debugging BUILD file evaluation
+      // TODO(bazel-team): add support for debugging skylark file evaluation
+      return DebugServerUtils.runWithDebuggingIfEnabled(
+          env, () -> Thread.currentThread().getName(), () -> doExec(env, eventHandler));
+    } catch (EvalException e) {
+      // runWithDebuggingIfEnabled() shouldn't throw EvalException, since doExec() does not
+      throw new AssertionError("Unexpected EvalException", e);
+    }
+  }
+
+  private boolean doExec(Environment env, EventHandler eventHandler) throws InterruptedException {
     boolean ok = true;
     for (Statement stmt : statements) {
       if (!execTopLevelStatement(stmt, env, eventHandler)) {
@@ -222,7 +234,7 @@
   public boolean execTopLevelStatement(Statement stmt, Environment env,
       EventHandler eventHandler) throws InterruptedException {
     try {
-      new Eval(env).exec(stmt);
+      Eval.fromEnvironment(env).exec(stmt);
       return true;
     } catch (EvalException e) {
       // Do not report errors caused by a previous parsing error, as it has already been
@@ -367,7 +379,7 @@
    */
   @Nullable public Object eval(Environment env) throws EvalException, InterruptedException {
     Object last = null;
-    Eval evaluator = new Eval(env);
+    Eval evaluator = Eval.fromEnvironment(env);
     for (Statement statement : statements) {
       if (statement instanceof ExpressionStatement) {
         last = ((ExpressionStatement) statement).getExpression().eval(env);
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/DebugFrame.java b/src/main/java/com/google/devtools/build/lib/syntax/DebugFrame.java
new file mode 100644
index 0000000..362739c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/DebugFrame.java
@@ -0,0 +1,58 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.syntax;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.events.Location;
+import javax.annotation.Nullable;
+
+/** The information about a single frame in a thread's stack trace relevant to the debugger. */
+@AutoValue
+public abstract class DebugFrame {
+  /** The source location where the frame is currently paused. */
+  @Nullable
+  public abstract Location location();
+
+  /** The name of the function that this frame represents. */
+  public abstract String functionName();
+
+  /**
+   * The local bindings associated with the current lexical frame. For the outer-most scope this
+   * will be empty.
+   */
+  public abstract ImmutableMap<String, Object> lexicalFrameBindings();
+
+  /** The global vars and builtins for this frame. May be shadowed by the lexical frame bindings. */
+  public abstract ImmutableMap<String, Object> globalBindings();
+
+  public static Builder builder() {
+    return new AutoValue_DebugFrame.Builder().setLexicalFrameBindings(ImmutableMap.of());
+  }
+
+  /** Builder class for {@link DebugFrame}. */
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder setLocation(@Nullable Location location);
+
+    public abstract Builder setFunctionName(String functionName);
+
+    public abstract Builder setLexicalFrameBindings(ImmutableMap<String, Object> bindings);
+
+    public abstract Builder setGlobalBindings(ImmutableMap<String, Object> bindings);
+
+    public abstract DebugFrame build();
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/DebugServer.java b/src/main/java/com/google/devtools/build/lib/syntax/DebugServer.java
new file mode 100644
index 0000000..f462550
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/DebugServer.java
@@ -0,0 +1,42 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.syntax;
+
+/** A debug server interface, called from core skylark code. */
+public interface DebugServer {
+
+  /**
+   * Tracks the execution of the given callable object in the debug server.
+   *
+   * @param env the Skylark execution environment
+   * @param threadName the descriptive name of the thread
+   * @param callable the callable object whose execution will be tracked
+   * @param <T> the result type of the callable
+   * @return the value returned by the callable
+   */
+  <T> T runWithDebugging(Environment env, String threadName, DebugCallable<T> callable)
+      throws EvalException, InterruptedException;
+
+  /** Represents an invocation that will be tracked as a thread by the Skylark debug server. */
+  interface DebugCallable<T> {
+
+    /**
+     * The invocation that will be tracked.
+     *
+     * @return the result
+     */
+    T call() throws EvalException, InterruptedException;
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/DebugServerUtils.java b/src/main/java/com/google/devtools/build/lib/syntax/DebugServerUtils.java
new file mode 100644
index 0000000..f653cd9
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/DebugServerUtils.java
@@ -0,0 +1,64 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.syntax;
+
+import com.google.devtools.build.lib.syntax.DebugServer.DebugCallable;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/** A helper class for enabling/disabling skylark debugging. */
+public final class DebugServerUtils {
+
+  private DebugServerUtils() {}
+
+  private static final AtomicReference<DebugServer> instance = new AtomicReference<>();
+
+  /**
+   * Called at the start of a debuggable skylark session to enable debugging. The custom {@link
+   * Eval} supplier provided should intercept statement execution to check for breakpoints.
+   */
+  public static void initializeDebugServer(
+      DebugServer server, Function<Environment, Eval> evalOverride) {
+    instance.set(server);
+    Eval.setEvalSupplier(evalOverride);
+  }
+
+  /** Called at the end of a debuggable skylark session to disable debugging. */
+  public static void disableDebugging() {
+    instance.set(null);
+    Eval.removeCustomEval();
+  }
+
+  /**
+   * Tracks the execution of the given callable object in the debug server.
+   *
+   * <p>If the skylark debugger is not enabled, runs {@code callable} directly.
+   *
+   * @param env the Skylark execution environment
+   * @param threadName the descriptive name of the thread
+   * @param callable the callable object whose execution will be tracked
+   * @param <T> the result type of the callable
+   * @return the value returned by the callable
+   */
+  public static <T> T runWithDebuggingIfEnabled(
+      Environment env, Supplier<String> threadName, DebugCallable<T> callable)
+      throws EvalException, InterruptedException {
+    DebugServer server = instance.get();
+    return server != null
+        ? server.runWithDebugging(env, threadName.get(), callable)
+        : callable.call();
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Debuggable.java b/src/main/java/com/google/devtools/build/lib/syntax/Debuggable.java
new file mode 100644
index 0000000..4540f55
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Debuggable.java
@@ -0,0 +1,80 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.syntax;
+
+import com.google.devtools.build.lib.events.Location;
+import java.util.Collection;
+import java.util.function.Predicate;
+import javax.annotation.Nullable;
+
+/** A context in which debugging can occur. Implemented by Skylark environments. */
+public interface Debuggable {
+
+  /** Evaluates a Skylark expression in the adapter's environment. */
+  Object evaluate(String expression) throws EvalException, InterruptedException;
+
+  /**
+   * Returns the stack frames corresponding of the context's current (paused) state.
+   *
+   * <p>For all stack frames except the innermost, location information is retrieved from the
+   * current context. The innermost frame's location must be supplied as {@code currentLocation} by
+   * the caller.
+   */
+  Collection<DebugFrame> listFrames(Location currentLocation);
+
+  /**
+   * Given a requested stepping behavior, returns a predicate over the context that tells the
+   * debugger when to pause.
+   *
+   * <p>The predicate will return true if we are at the next statement where execution should pause,
+   * and it will return false if we are not yet at that statement. No guarantee is made about the
+   * predicate's return value after we have reached the desired statement.
+   *
+   * <p>A null return value indicates that no further pausing should occur.
+   */
+  @Nullable
+  ReadyToPause stepControl(Stepping stepping);
+
+  /**
+   * When stepping, this determines whether or not the context has yet reached a state for which
+   * execution should be paused.
+   *
+   * <p>A single instance is only useful for advancing by one pause. A new instance may be required
+   * after that.
+   */
+  interface ReadyToPause extends Predicate<Environment> {}
+
+  /** Describes the stepping behavior that should occur when execution of a thread is continued. */
+  enum Stepping {
+    /** Continue execution without stepping. */
+    NONE,
+    /**
+     * If the thread is paused on a statement that contains a function call, step into that
+     * function. Otherwise, this is the same as OVER.
+     */
+    INTO,
+    /**
+     * Step over the current statement and any functions that it may call, stopping at the next
+     * statement in the same frame. If no more statements are available in the current frame, same
+     * as OUT.
+     */
+    OVER,
+    /**
+     * Continue execution until the current frame has been exited and then pause. If we are
+     * currently in the outer-most frame, same as NONE.
+     */
+    OUT,
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
index c536a68..6f84188 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
@@ -16,6 +16,7 @@
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
 import com.google.devtools.build.lib.cmdline.Label;
@@ -31,10 +32,12 @@
 import com.google.devtools.build.lib.util.Fingerprint;
 import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.lib.util.SpellChecker;
+import com.google.devtools.build.lib.vfs.PathFragment;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -75,7 +78,7 @@
  * that the words "dynamic" and "static" refer to the point of view of the source code, and here we
  * have a dual point of view.
  */
-public final class Environment implements Freezable {
+public final class Environment implements Freezable, Debuggable {
 
   /**
    * A phase for enabling or disabling certain builtin functions
@@ -1150,6 +1153,91 @@
     return vars;
   }
 
+  private static final class EvalEventHandler implements EventHandler {
+    List<String> messages = new ArrayList<>();
+
+    @Override
+    public void handle(Event event) {
+      if (event.getKind() == EventKind.ERROR) {
+        messages.add(event.getMessage());
+      }
+    }
+  }
+
+  @Override
+  public Object evaluate(String expression) throws EvalException, InterruptedException {
+    ParserInputSource inputSource =
+        ParserInputSource.create(expression, PathFragment.create("<debug eval>"));
+    EvalEventHandler eventHandler = new EvalEventHandler();
+    Expression expr = Parser.parseExpression(inputSource, eventHandler);
+    if (!eventHandler.messages.isEmpty()) {
+      throw new EvalException(expr.getLocation(), eventHandler.messages.get(0));
+    }
+    return expr.eval(this);
+  }
+
+  @Override
+  public ImmutableList<DebugFrame> listFrames(Location currentLocation) {
+    ImmutableList.Builder<DebugFrame> frameListBuilder = ImmutableList.builder();
+
+    Continuation currentContinuation = continuation;
+    Frame currentFrame = currentFrame();
+
+    // if there's a continuation then the current frame is a lexical frame
+    while (currentContinuation != null) {
+      frameListBuilder.add(
+          DebugFrame.builder()
+              .setLexicalFrameBindings(ImmutableMap.copyOf(currentFrame.getTransitiveBindings()))
+              .setGlobalBindings(ImmutableMap.copyOf(getGlobals().getTransitiveBindings()))
+              .setFunctionName(currentContinuation.function.getFullName())
+              .setLocation(currentLocation)
+              .build());
+
+      currentFrame = currentContinuation.lexicalFrame;
+      currentLocation = currentContinuation.caller.getLocation();
+      currentContinuation = currentContinuation.continuation;
+    }
+
+    frameListBuilder.add(
+        DebugFrame.builder()
+            .setGlobalBindings(ImmutableMap.copyOf(getGlobals().getTransitiveBindings()))
+            .setFunctionName("<top level>")
+            .setLocation(currentLocation)
+            .build());
+
+    return frameListBuilder.build();
+  }
+
+  @Override
+  @Nullable
+  public ReadyToPause stepControl(Stepping stepping) {
+    final Continuation pausedContinuation = continuation;
+
+    switch (stepping) {
+      case NONE:
+        return null;
+      case INTO:
+        // pause at the very next statement
+        return env -> true;
+      case OVER:
+        return env -> isAt(env, pausedContinuation) || isOutside(env, pausedContinuation);
+      case OUT:
+        // if we're at the outer-most frame, same as NONE
+        return pausedContinuation == null ? null : env -> isOutside(env, pausedContinuation);
+    }
+    throw new IllegalArgumentException("Unsupported stepping type: " + stepping);
+  }
+
+  /** Returns true if {@code env} is in a parent frame of {@code pausedContinuation}. */
+  private static boolean isOutside(Environment env, @Nullable Continuation pausedContinuation) {
+    return pausedContinuation != null && env.continuation == pausedContinuation.continuation;
+  }
+
+  /** Returns true if {@code env} is at the same frame as {@code pausedContinuation. */
+  private static boolean isAt(Environment env, @Nullable Continuation pausedContinuation) {
+    return env.continuation == pausedContinuation;
+  }
+
   @Override
   public int hashCode() {
     throw new UnsupportedOperationException(); // avoid nondeterminism
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Eval.java b/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
index 98130ae..56c98a7 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
@@ -18,13 +18,14 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
 
 /**
  * Evaluation code for the Skylark AST. At the moment, it can execute only statements (and defers to
  * Expression.eval for evaluating expressions).
  */
 public class Eval {
-  private final Environment env;
+  protected final Environment env;
 
   /** An exception that signals changes in the control flow (e.g. break or continue) */
   private static class FlowException extends EvalException {
@@ -38,11 +39,31 @@
     }
   }
 
+  public static Eval fromEnvironment(Environment env) {
+    return evalSupplier.apply(env);
+  }
+
+  public static void setEvalSupplier(Function<Environment, Eval> evalSupplier) {
+    Eval.evalSupplier = evalSupplier;
+  }
+
+  /** Reset Eval supplier to the default. */
+  public static void removeCustomEval() {
+    evalSupplier = Eval::new;
+  }
+
+  // TODO(bazel-team): remove this static state in favor of storing Eval instances in Environment
+  private static Function<Environment, Eval> evalSupplier = Eval::new;
+
   private static final FlowException breakException = new FlowException("FlowException - break");
   private static final FlowException continueException =
       new FlowException("FlowException - continue");
 
-  public Eval(Environment env) {
+  /**
+   * This constructor should never be called directly. Call {@link #fromEnvironment(Environment)}
+   * instead.
+   */
+  protected Eval(Environment env) {
     this.env = env;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java
index eda6c3d..4db184e 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java
@@ -76,7 +76,7 @@
         env.update(name, arguments[i++]);
       }
 
-      Eval eval = new Eval(env);
+      Eval eval = Eval.fromEnvironment(env);
       try {
         for (Statement stmt : statements) {
           if (stmt instanceof ReturnStatement) {