// Copyright 2014 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.common.base.Joiner; import com.google.common.base.Throwables; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.util.LoggingUtil; import com.google.devtools.build.lib.util.Preconditions; import java.util.logging.Level; import javax.annotation.Nullable; /** * Exceptions thrown during evaluation of BUILD ASTs or Skylark extensions. * * <p>This exception must always correspond to a repeatable, permanent error, i.e. evaluating the * same package again must yield the same exception. Notably, do not use this for reporting I/O * errors. * * <p>This requirement is in place so that we can cache packages where an error is reported by way * of {@link EvalException}. */ public class EvalException extends Exception { @Nullable private Location location; private final String message; private final boolean dueToIncompleteAST; private static final Joiner LINE_JOINER = Joiner.on("\n").skipNulls(); private static final Joiner FIELD_JOINER = Joiner.on(": ").skipNulls(); /** * @param location the location where evaluation/execution failed. * @param message the error message. */ public EvalException(Location location, String message) { this.location = location; this.message = Preconditions.checkNotNull(message); this.dueToIncompleteAST = false; } public EvalException(Location location, String message, String url) { this.location = location; this.dueToIncompleteAST = false; this.message = Preconditions.checkNotNull(message) + "\n" + "Need help? See " + Preconditions.checkNotNull(url); } /** * @param location the location where evaluation/execution failed. * @param message the error message. * @param dueToIncompleteAST if the error is caused by a previous error, such as parsing. */ public EvalException(Location location, String message, boolean dueToIncompleteAST) { this(location, message, dueToIncompleteAST, true); } /** * Create an EvalException with the option to not fill in the java stack trace. An optimization * for ReturnException, and potentially others, which aren't exceptional enough to include a * stack trace. * * @param location the location where evaluation/execution failed. * @param message the error message. * @param dueToIncompleteAST if the error is caused by a previous error, such as parsing. * @param fillInJavaStackTrace whether or not to fill in the java stack trace for this exception */ EvalException( Location location, String message, boolean dueToIncompleteAST, boolean fillInJavaStackTrace) { super(null, null, /*enableSuppression=*/ true, fillInJavaStackTrace); this.location = location; this.message = Preconditions.checkNotNull(message); this.dueToIncompleteAST = dueToIncompleteAST; } /** * @param location the location where evaluation/execution failed. * @param message the error message. * @param cause a Throwable that caused this exception. */ public EvalException(Location location, String message, Throwable cause) { super(cause); this.location = location; // This is only used from Skylark, it's useful for debugging. this.message = FIELD_JOINER.join(message, getCauseMessage(message)); if (this.message.isEmpty()) { String details; if (cause == null) { details = "Invalid EvalException: no cause given!"; } else { details = "Invalid EvalException:\n" + Throwables.getStackTraceAsString(cause); } LoggingUtil.logToRemote(Level.SEVERE, details, cause); throw new IllegalArgumentException(details); } this.dueToIncompleteAST = false; } public EvalException(Location location, Throwable cause) { this(location, null, cause); } /** * Returns the error message with location info if exists. */ public String print() { // TODO(bazel-team): do we also need a toString() method? return LINE_JOINER.join("\n", FIELD_JOINER.join(getLocation(), message), (dueToIncompleteAST ? "due to incomplete AST" : ""), getCauseMessage(message)); } /** * @param message the message of this exception, so far. * @return a message for the cause of the exception, if the main message (passed as argument) * doesn't already contain this cause; return null if no new information is available. */ private String getCauseMessage(String message) { Throwable cause = getCause(); if (cause == null) { return null; } String causeMessage = cause.getMessage(); if (causeMessage == null) { return null; } if (message == null) { return causeMessage; } // Skip the cause if it is redundant with the message so far. if (message.contains(causeMessage)) { return null; } return causeMessage; } /** * Returns the error message. */ @Override public String getMessage() { return message; } /** * Returns the location of the evaluation error. */ @Nullable public Location getLocation() { return location; } /** * Returns a boolean that tells whether this exception was due to an incomplete AST */ public boolean isDueToIncompleteAST() { return dueToIncompleteAST; } /** * Ensures that this EvalException has proper location information. * Does nothing if the exception already had a location, or if no location is provided. * @return this EvalException, in fluent style. */ public EvalException ensureLocation(Location loc) { if (location == null && loc != null) { location = loc; } return this; } /** * Returns whether this exception can be added to a stack trace created by {@link * EvalExceptionWithStackTrace}. */ public boolean canBeAddedToStackTrace() { return true; } /** * A class to support a special case of EvalException when the cause of the error is an * Exception during a direct Java call. Allow the throwing code to provide context in a message. */ public static final class EvalExceptionWithJavaCause extends EvalException { /** * @param location the location where evaluation/execution failed. * @param message the error message. * @param cause a Throwable that caused this exception. */ public EvalExceptionWithJavaCause(Location location, String message, Throwable cause) { super(location, message, cause); } /** * @param location the location where evaluation/execution failed. * @param cause a Throwable that caused this exception. */ public EvalExceptionWithJavaCause(Location location, Throwable cause) { this(location, null, cause); } } }