aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/syntax')
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/BuildFileAST.java16
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/DebugFrame.java58
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/DebugServer.java42
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/DebugServerUtils.java64
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/Debuggable.java80
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/Environment.java90
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/Eval.java25
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java2
8 files changed, 371 insertions, 6 deletions
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 439e4c5f50..ca6307fa8a 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 @@ public class BuildFileAST extends ASTNode {
* @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 class BuildFileAST extends ASTNode {
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 @@ public class BuildFileAST extends ASTNode {
*/
@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 0000000000..362739ca93
--- /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 0000000000..f462550797
--- /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 0000000000..f653cd994f
--- /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 0000000000..4540f55dfb
--- /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 c536a688b3..6f84188b4b 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 @@ package com.google.devtools.build.lib.syntax;
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.syntax.Mutability.MutabilityException;
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 @@ import javax.annotation.Nullable;
* 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 @@ public final class Environment implements Freezable {
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 98130ae3ff..56c98a7140 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 com.google.common.collect.ImmutableList;
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 class Eval {
}
}
+ 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 eda6c3d0b8..4db184eceb 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 @@ public class UserDefinedFunction extends BaseFunction {
env.update(name, arguments[i++]);
}
- Eval eval = new Eval(env);
+ Eval eval = Eval.fromEnvironment(env);
try {
for (Statement stmt : statements) {
if (stmt instanceof ReturnStatement) {